Sending Mail






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, I'll use it throughout this recipe. ActionMailer uses TMail under the covers, and it's provided by the actionmailer gem.

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

	require 'rubygems'
	require 'action_mailer'

	class SimpleMailer < ActionMailer::Base
	  def simple_message(recipient)
	     from '[email protected]'
	     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('[email protected]')
	# From: [email protected]
	# To: [email protected]
	# 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, you'll need to tell ActionMailer about your SMTP server. If you're sending mail from example.org and you've 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('[email protected]')

If you're using your ISP's SMTP server, you'll probably need to send authentication information so the server knows you're 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 => '[email protected]',
	                                       :password => 'my_password',
	                                       :authentication => :login }

	SimpleMailer.deliver_simple_message('[email protected]')

Discussion

Unless you're writing a general-purpose mail client, you probably won't be letting your users compose emails from scratch. More likely, you'll 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.

Here's 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 '[email protected]'
	    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('[email protected]',
	                                           'email_test')

Here it is in action:

	Dir.mkdir('email_test')
	open('email_test/image.jpg', 'wb') { |f| f << "\377\330\377\340\000\020JFIF" }
	open('email_test/text.txt', 'w') { |f| f << "Here's some text." }

	puts SimpleMailer.create_directory_dump_message('[email protected]',
	                                                'email_test')
	# From: [email protected]
	# To: [email protected]
	# 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
	#
	# Here's some text.=
	#
	# --mimepart_443d73ecc651_3ae1..fdbeb1ba4328--

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

	require 'net/smtp'
	Net::SMTP.start('smtp.example.org', 25, 'example.org',
	                '[email protected]', '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



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows