Sending Mail

Problem

You want to send an email message, either an autogenerated one or one entered in by an end user.

Solution

First you need to turn the parts of the email message into a single string, representing the whole message complete with headers and/or attachments. You can construct the string manually or use a number of libraries, including RubyMail, TMail, and ActionMailer. Since ActionMailer is one of the dependencies of Rails, Ill use it throughout this recipe. ActionMailer uses TMail under the covers, and its provided by the actionmailer gem.

Here, I use ActionMailer to construct a simple, single-part email message:

require ubygems require action_mailer class SimpleMailer < ActionMailer::Base def simple_message(recipient) from leonardr@example.org recipients recipient subject A single-part message for you body This message has a plain text body. end end

ActionMailer then makes two new methods available for generating this kind of email message: SimpleMailer. create_simple_message, which returns the email message as a data structure, and SimpleMailer. deliver_simple_message, which actually sends the message.

puts SimpleMailer. create_simple_message(lucas@example.com) # From: leonardr@example.org # To: lucas@example.com # Subject: A single-part message for you # Content-Type: text/plain; charset=utf-8 # # This message has a plain text body.

To deliver the message, call deliver_simple_message instead of create_simple_message. First, though, youll need to tell ActionMailer about your SMTP server. If you e sending mail from example.org and youve got an SMTP server on the local machine, you might send a message this way:

ActionMailer::Base.server_settings = { :address => localhost, :port => 25, # 25 is the default :domain => example.org } SimpleMailer.deliver_simple_message(lucas@example.com)

If you e using your ISPs SMTP server, youll probably need to send authentication information so the server knows you e not a spammer. Your ActionMailer setup will probably look like this:

ActionMailer::Base.server_settings = { :address => smtp.example.org, :port => 25, :domain => example.org, :user_name => leonardr@example.org, :password => my_password, :authentication => :login } SimpleMailer.deliver_simple_message(lucas@example.com)

Discussion

Unless you e writing a general-purpose mail client, you probably won be letting your users compose emails from scratch. More likely, youll define a template for every type of email your application might send, and fill it in with custom data every time you send a message.[1]

[1] You can use ActionMailer even if you are writing a general-purpose mail client (just write a single hook method called custom_messge that takes a whole lot of arguments), but you might prefer to drop down a level and use TMail or RubyMail.

This is what ActionMailer is designed for. The simple_message method defined above is actually a hook method that makes ActionMailer respond to two other methods: create_simple_message and deliver_simple_message. The hook method defines the headers and body of a message template, the create_ method instantiates the template with specific values, and the deliver_ method actually delivers the email. You never call simple_message directly.

Within your hook method, you can set most of the standard email headers by calling a method of the same name (subject, cc, and so on). You can also set custom headers by modifying the @headers instance variable:

class SimpleMailer def headerful_message @headers[A custom header] = Its value body Body end end puts SimpleMailer.create_headerful_message # Content-Type: text/plain; charset=utf-8 # A custom header: Its value # # Body

You can create a multipart message with attachments by passing the MIME type of the attachment into the attachment method.

Heres a method that creates a message containing a dump of the files in a directory (perhaps a bunch of logfiles). It uses the mime-types gem to determine the probable MIME type of a file, based on its filename:

require mime/types class SimpleMailer def directory_dump_message(recipient, directory) from directory-dump@example.org recipients recipient subject "Dump of #{directory}" body %{Here are the files currently in "#{directory}":} Dir.new(directory).each do |f| path = File.join(directory, f) if File.file? path mime_type = MIME::Types.of(f).first content_type = (mime_type ? mime_type.content_type : application/binary) attachment(content_type) do |a| a.body = File.read(path) a.filename = f a.transfer_encoding = quoted-printable if content_type =~ /^text// end end end end end SimpleMailer.create_directory_dump_message(lucas@example.com, email_test)

Here it is in action:

Dir.mkdir(email_test) open(email_test/image.jpg, wb) { |f| f << "3773303773400020JFIF" } open(email_test/text.txt, w) { |f| f << "Heres some text." } puts SimpleMailer.create_directory_dump_message(lucas@example.com, email_test) # From: directory-dump@example.org # To: lucas@example.com # Subject: Dump of email_test # Mime-Version: 1.0 # Content-Type: multipart/mixed; boundary=mimepart_443d73ecc651_3ae1..fdbeb1ba4328 # # # --mimepart_443d73ecc651_3ae1..fdbeb1ba4328 # Content-Type: text/plain; charset=utf-8 # Content-Disposition: inline # # Here are the files currently in "email_test": # --mimepart_443d73ecc651_3ae1..fdbeb1ba4328 # Content-Type: image/jpeg; name=image.jpg # Content-Transfer-Encoding: Base64 # Content-Disposition: attachment; filename=image.jpg # # /9j/4AAQSkZJRg== # # --mimepart_443d73ecc651_3ae1..fdbeb1ba4328 # Content-Type: text/plain; name=text.txt # Content-Transfer-Encoding: Quoted-printable # Content-Disposition: attachment; filename=text.txt # # Heres some text.= # # --mimepart_443d73ecc651_3ae1..fdbeb1ba4328--

If you e a minimalist, you can use the net/smtp library to send email without installing any gems. Theres nothing in the Ruby standard library to help you with creating the email string, though; youll have to build it manually. Once youve got the string, you can send it as an email message with code like this:

require et/smtp Net::SMTP.start(smtp.example.org, 25, example.org, leonardr@example.org, my_password, :login) do |smtp| smtp.send_message(message_string, from_address, to_address) end

Whether you use Net::SMTP or ActionMailer to deliver your mail, the possible SMTP authentication schemes are represented with symbols (:login, :plain, and :cram_md5). Any given SMTP server may support any or all of these schemes. Try them one at a time, or ask your system administrator or ISP which one to use.

See Also

Категории