# # File:: email.rb # Description:: Email sending functions. # # Author:: David Muir # Date:: 13 September 2008 # #----------------------------------------------------------------------------- # Uses #----------------------------------------------------------------------------- require 'pipeline/os/path' require 'pipeline/util/string' require 'base64' require 'net/smtp' require 'tmail' # Monkey-patching for debugging. module TMail class Mail TO_TREE_INSPECT_DEFAULT = proc { |node| "#" } def to_tree io='', &inspect inspect ||= TO_TREE_INSPECT_DEFAULT io << "- #{inspect[self]}\n" recurse = proc do |node, prefix| if node.multipart? node.parts.each_with_index do |child, i| a, b = i == parts.length - 1 ? ["\\", ' '] : ['|', '| '] io << "#{prefix + a}- #{inspect[child]}\n" recurse.call child, prefix + b end end end recurse.call self, ' ' io end end end #----------------------------------------------------------------------------- # Implementation #----------------------------------------------------------------------------- module Pipeline module Util # # == Description # Object representing an email address, including the address String and # an (optional) alias String. # class EmailAddress attr_reader :address attr_reader :alias # Class constructor. def initialize( address, friendly = nil ) @address = address @alias = friendly end # Return String representation of object. def to_s( ) if ( @alias.nil? ) then "#{@alias} <#{@address}>" else "#{@address}" end end #-------------------------------------------------------------------- # Class Methods #-------------------------------------------------------------------- # Constructor from REXML::Element (expects 'alias' and 'address' attributes). def EmailAddress::from_xml( xml_node ) address = xml_node.attributes['address'] friendly = xml_node.attributes['alias'] EmailAddress.new( address, friendly ) end # Constructor from REXML::Element (child email elements as above). def EmailAddress::from_xml_list( xml_node ) emails = [] xml_node.each_element do |address_node| emails << EmailAddress::from_xml( address_node ) end emails end end # # == Description # Email sending utility class. The class can be used in two ways depending # on the required use case. # # If multiple emails are to be sent then you can create an instance of the # Email object with the relevant server and port settings. If only a # single email is required then the Email::send class method may be simpler # to use. # # See also Pipeline::Util::EmailFactory. # # == Example Usage # See individual function examples and unit test cases. # class Email DEFAULT_SMTP_PORT = 25 DEFAULT_AUTH_MODE = :plain attr_reader :server attr_reader :port attr_reader :domain # We deliberately do not allow access to the username and password. # # Constructor. The +options+ argument is a Hash containing the # following keys: # :server # :port # :domain # :username # :password # :auth_mode # def initialize( options ) throw ArgumentError.new( "Invalid options, must be Hash object." ) \ unless ( options.is_a?( Hash ) ) @server = options[:server] @port = options[:port] @domain = options[:domain] @username = options[:username] @password = options[:password] @auth_mode = options[:auth_mode] end # # Send an email message to a set of recipients. The +from+, +to+ and # optional +reply_to+ arguments must be hashes using :address and :alias # keys to supply the email address string and aliases for users. # # To send an HTML email set the +content_type+ argument to 'text/html'. # def send( from, to, subject, body, content_type = 'text/plain', reply_to = nil ) Email::send( @server, @port, @username, @password, @domain, @auth_mode, from, to, subject, body, content_type, reply_to ) end # # Send a multipart email message to a set of recipients. See # documentation for the +send+ method. # # The +parts+ argument must be an TMail::Mail structure. See # http://rubyforge.org/pipermail/tmail-talk/2008-July/000086.html for # an example of constructing such multipart TMail::Mail object # hierarchies. # # MIME type strings can be found at: # http://www.webmaster-toolkit.com/mime-types.shtml # def send_multipart( from, to, subject, parts, reply_to = nil ) Email::send_multipart( @server, @port, @username, @password, @domain, @auth_mode, from, to, subject, parts, reply_to ) end # # Send an email message to a set of recipients. The +from+, +to+ and # optional +reply_to+ arguments must be Hash objects using :address # and :alias keys to supply the email address string and aliases for # users. # # To send an HTML email set the +content_type+ argument to 'text/html'. # # +auth_mode+ should be set to one of :plain, :login or :cram_md5. # # == Example Usage # Email::send( 'mailrelay', 25, 'username', 'password', 'domain.com', :plain, # { :address => 'test@rockstarnorth.com', :alias => 'Email Test' }, # { :address => 'david.muir@rockstarnorth.com', :alias => 'David Muir' }, # 'Testing Email', # "Testing body plain text (#{Time.now})" ) # def Email::send( server, port, username, password, domain, auth_mode, from, to, subject, body, content_type = 'text/plain', reply_to = nil ) throw ArgumentError.new( 'from argument must be a Hash' ) \ unless ( from.is_a?( Hash ) ) throw ArgumentError.new( 'to argument must be a Hash or Array' ) \ unless ( ( to.is_a?( Hash ) ) or ( to.is_a?( Array ) ) ) throw ArgumentError.new( 'port must be an Integer' ) \ unless ( port.is_a?( Integer ) ) mail = TMail::Mail.new() mail.mime_version = '1.0' to_addresses = [] if ( to.is_a?( Hash ) ) then to_addresses << to[:address] mail.to = "#{to[:alias]} <#{to[:address]}>" elsif ( to.is_a?( Array ) ) then mailto = "" to.each_with_index do |recipient, index| throw ArgumentError.new( 'to argument array contains a non-Hash object.' ) \ unless ( recipient.is_a?( Hash ) ) to_addresses << recipient[:address] if ( index == ( to.size - 1 ) ) then mailto += "#{recipient[:alias]} <#{recipient[:address]}>" else mailto += "#{recipient[:alias]} <#{recipient[:address]}>, " end end mail.to = mailto end mail.from = "#{from[:alias]} <#{from[:address]}>" mail.reply_to = "#{reply_to[:alias]} <#{reply_to[:address]}>" unless ( reply_to.nil? ) mail.subject = subject mail.content_type = content_type mail.body = body if ( username.nil? and password.nil? and domain.nil? and auth_mode.nil? ) then Net::SMTP.start( server, port ) do |smtp| smtp.send_message( mail.to_s, from[:address], to_addresses ) end else Net::SMTP.start( server, port, domain, username, password, auth_mode ) do |smtp| smtp.send_message( mail.to_s, from[:address], to_addresses ) end end end # # Send a multipart email message to a set of recipients. The +from+, # +to+ and optional +reply_to+ arguments must be Hash objects using # :address and :alias keys to suplpy the email address string and # aliases for users. # # The +parts+ argument must be an TMail::Mail structure. See # http://rubyforge.org/pipermail/tmail-talk/2008-July/000086.html for # an example of constructing such multipart TMail::Mail object # hierarchies. # # MIME type strings can be found at: # http://www.webmaster-toolkit.com/mime-types.shtml # # == Example Usage # DHM TODO # def Email::send_multipart( server, port, username, password, domain, auth_mode, from, to, subject, parts, reply_to = nil ) throw ArgumentError.new( 'from argument must be a Hash' ) \ unless ( from.is_a?( Hash ) ) throw ArgumentError.new( 'to argument must be a Hash or Array' ) \ unless ( ( to.is_a?( Hash ) ) or ( to.is_a?( Array ) ) ) throw ArgumentError.new( 'port must be an Integer' ) \ unless ( port.is_a?( Integer ) ) throw ArgumentError.new( 'parts must be an Array' ) \ unless ( parts.is_a?( Array ) ) mail = TMail::Mail.new( ) mail.mime_version = '1.0' if ( to.is_a?( Hash ) ) then mail.to = "#{to[:alias]} <#{to[:address]}>" elsif ( to.is_a?( Array ) ) then mail.to = '' to.each do |recipient| throw ArgumentError.new( 'to argument array contains a non-Hash object.' ) \ unless ( recipient.is_a?( Hash ) ) mail.to = "#{recipient[:alias]} <#{recipient[:address]}>" end end mail.from = "#{from[:alias]} <#{from[:address]}>" mail.reply_to = "#{reply_to[:alias]} <#{reply_to[:address]}>" unless ( reply_to.nil? ) mail.subject = subject mail.set_content_type( 'multipart', 'mixed', { 'boundary' => TMail::new_boundary() } ) parts.each do |part| mail.parts << part; end puts mail.to_tree Net::SMTP.start( server, port, domain, username, password, auth_mode ) do |smtp| smtp.send_message( mail.to_s, from[:address], [ to[:address] ] ) end end end # # == Description # The EmailFactory can be used to create TMail::Mail objects for use with # the Email::send_multipart function. Factory functions are available for # creating some common types of TMail::Mail objects (e.g. plain text and # HTML messages, picture attachments etc). # class EmailFactory # # Create a TMail::Mail object given a HTML message body string. The # string is expected to be HTML formatted, starting with a tag, # ending with a tag. # def EmailFactory::create_html_mail( body ) throw ArgumentError.new( 'Invalid body argument, String expected.' ) \ unless ( body.is_a?( String ) ) #throw ArgumentError.new( 'body does not start with .' ) \ # unless ( body.starts_with( '' ) ) #throw ArgumentError.new( 'body does not end with .' ) \ # unless ( body.ends_with( '' ) ) html = TMail::Mail.new( ) html.mime_version = '1.0' html.content_type = 'text/html' html.body = body html end # # Create a TMail::Mail object given a message body string. The string # is expected to be plain text. # def EmailFactory::create_text_mail( body ) throw ArgumentError.new( 'Invalid body argument, String expected.' ) \ unless ( body.is_a?( String ) ) text = TMail::Mail.new( ) text.mime_version = '1.0' text.content_type = 'text/plain' text.body = body text end def EmailFactory::create_attachment_from_file( filename ) throw IOError.new( 'File does not exist' ) unless ( ::File::exists?( filename ) ) # Read file data. data = nil File.open( filename, 'rb' ) do |fp| data = fp.read( ) end attachment = TMail::Mail.new( ) attachment.mime_version = '1.0' attachment.body = Base64::encode64( data ) attachment.transfer_encoding = 'Base64' attachment.content_type = 'application/octet-stream' attachment['Content-Disposition'] = "attachment; filename=#{OS::Path::get_filename(filename)}" attachment end end end # Util module end # Pipeline module # email.rb