Ruby on Rails
HowToSendMimeMultipartEmailsWithActionMailer

An example from the ActionMailler API docs


    # attachments
    def signup_notification(recipient)
      recipients      recipient.email_address_with_name
      subject         "New account information" 
      from            "system@example.com"

      attachment :content_type => “image/jpeg”,
        :body => File.read(“an-image.jpg”)

      attachment “application/pdf” do |a|
        a.body = generate_your_pdf_here()
      end
    end

You can easily make your own parts and subparts too Check the official ActionMailer API docs first! If they miss stuff look for comments in the ActionMailer source.

The trick for sub-sub-parts is simply to give a block to the part() method.
Here’s an example with an explicit text/plain and text/html message content, followed by an attachment.


class PackageMailer < ActionMailer::Base

  def some_mail(binary_contents)
    @subject    = "mail with subparts" 
    @recipients = "blah" 

    part( :content_type => "multipart/alternative" ) do |p|
      p.part( :content_type => "text/plain",
                 :body => render_message("some_mail.text.plain.rhtml", {}) )

      p.part( :content_type => "text/html",
                 :body => render_message("some_mail.text.html.rhtml", {}) )
    end

    attachment( :content_type => "application/octet-stream",
                :body => binary_contents,
                :filename => "some_attachment" )
  end
end

Note that attachments are just a special kind of parts.

The section below details how to work with TMail to create Mime multipart messages. It it no longer relevant since ActionMailler now provides it’s own interface for creating those messages.

The Old Approach

First, see HowToSendEmailsWithActionMailer, as that gives the foundation for creating a mailer object. Pay specific attention to the section on Attachments, because it deals with creation of the raw TMail object. Ok…

In this case, I’ll make a text email and attach a CSV file.

Create the raw email

With any multipart email, you need to have a base to add the parts on to. The TMail object treats each part of the whole email as TMail::Mail object. In this way, you can add many different email parts together (attachments, html, text) together fairly simply.

To start, make a container Mail message:

mail = TMail::Mail.new
mail.date = Time.now
mail.set_content_type 'multipart', 'mixed'
mail.mime_version = '1.0'
mail.body = nil

It doesn’t need much, because it doesn’t do much :)

WARNING: The code above does not work with TMail and causes an error… The set_content_type has to be set after all parts are added and not before!

Create the text body

The text body of the email gets attached next. Make a standard Mailer class and use the “create_” style call to make the TMail object without sending it.

message = MyMailer.create_my_email
message.set_content_type('text', 'plain')
message.transfer_encoding = '7bit'

Once you’ve got that object, and you’ve got the MIME params set up, just push it onto the container TMail message

mail.parts.push(message)

Add the Attachment

This is very similar to the first step, as we have to make a mail object that used for the container:

filename = "report.csv" 
encoded_file = "1,1,1\n" 

attachment = TMail::Mail.new
attachment.body = encoded_file
attachment.transfer_encoding = '7bit'
attachment.set_content_type('application', 'csv', 'name' => filename)

attachment.header["Content-Disposition"] = "attachment; filename=" + filename

Once again, to add the ‘attachment’ to the ‘container’:

mail.parts.push(attachment)

You might notice that for the content-type I used a method, but for the content-disposition I used the hash; This is only because the following line threw an exception for me.

attachment.set_content_disposition('attachment', 'filename' => filename)

Note you could just as easily create a mailer whose template is the file body and you pass in the data, having the mailer render the file, but that did not suit me at the time :)

Send the email

Seeing as we already have a reference to the \MyMailer object, use it to send. The mail.writeback call ‘flattens’ the TMail objects out into a MIME message ready for sending.

mail.write_back
MyMailer::deliver(mail)