Deep in Rails: ActionMailer#deliver Part V
So now we have our mail object. Let’s send it.
For the sending of the message, we need to go back to the beginning, method_missing:
(line 62 action_mailer/base.rb)
def method_missing(method_symbol, *parameters) #:nodoc:
if match = matches_dynamic_method?(method_symbol)
case match[1]
when 'create' then new(match[2], *parameters).mail
when 'deliver' then new(match[2], *parameters).deliver!
when 'new' then nil
else super
end
else
super
end
end
You remember method_missing from a long time ago. We just finished the first part of the line:
when 'deliver' then new(match[2], *parameters).deliver!
We created the new AM object and now we call deliver! on it. That’s our next stop:
(line 520 action_mailer/base.rb)
def deliver!(mail = @mail)
raise "no mail object available for delivery!" unless mail
unless logger.nil?
logger.info "Sent mail to #{Array(recipients).join(', ')}"
logger.debug "\n#{mail.encoded}"
end
begin
__send__("perform_delivery_#{delivery_method}", mail) if perform_deliveries
rescue Exception => e # Net::SMTP errors or sendmail pipe errors
raise e if raise_delivery_errors
end
return mail
end
The beginning of this method does some checking and logging. The main part of this method is:
__send__("perform_delivery_#{delivery_method}", mail) if perform_deliveries
We are again using __send__ to bypass any possible overridden method and calling perform_delivery_#{delivery_method} with the mail object. delivery_method is a configuration attribute on the AM class along with perform_deliveries. AM defaults to “smtp” for delivery_method and “true” for perform_deliveries so the method we end up calling in our example is perform_delivery_smtp. Let’s see that code:
(line 680 action_mailer/base.rb)
def perform_delivery_smtp(mail)
destinations = mail.destinations
mail.ready_to_send
sender = (mail['return-path'] && mail['return-path'].spec) || mail.from
smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port])
smtp.enable_starttls_auto if smtp_settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto)
smtp.start(smtp_settings[:domain], smtp_settings[:user_name], smtp_settings[:password],
smtp_settings[:authentication]) do |smtp|
smtp.sendmail(mail.encoded, sender, destinations)
end
end
This is the finale. The climax. The whole reason we even engage in ActionMailer, to actualy send an email. Here we go.
The first line pulls the destinations (recipients) from the mail object and stores them in a variable. Next, we call ready_to_send on the mail object which is also the TMail object (remember that we assigned the final TMail object to the @mail attribute on our AM object). This method is defined in TMail::Net:
(line 62 action_mailer/tmail-1.2.3/tmail-1.2.3/net.rb)
def ready_to_send
delete_no_send_fields
add_message_id
add_date
end
NOSEND_FIELDS = %w(
received
bcc
)
def delete_no_send_fields
NOSEND_FIELDS.each do |nm|
delete nm
end
delete_if {|n,v| v.empty? }
end
def add_message_id( fqdn = nil )
self.message_id = ::TMail::new_message_id(fqdn)
end
def add_date
self.date = Time.now
end
Now, if you scan through that code you may notice that we are calling delete and delete_if like we were in a standard Enumerable object, but we are not. TMail also defines those methods:
def delete( key )
@header.delete key.downcase
end
def delete_if
@header.delete_if do |key,val|
if Array === val
val.delete_if {|v| yield key, v }
val.empty?
else
yield key, val
end
end
end
What we are doing with all this is stripping and setting some defaults. delete_no_send_fields get’s rid of the “received” and “bcc” keys from our header and we are getting rid of any headers that have empty values. After that we set a nice new hex type id for this email with TMail.new_message_id which is defined in TMail::Utils which gives us a string with a hex code attached to your machine’s hostname. The last line adds the date to the mail message and that’s it.
The next line sets the sender attribute.
sender = (mail['return-path'] && mail['return-path'].spec) || mail.from
Now we get into the actual sending of the email. We aren’t going to go into it because it is part of Ruby’s core lib, but we will quickly skim over it to see what’s going on:
smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port])
smtp.enable_starttls_auto if smtp_settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto)
smtp.start(smtp_settings[:domain], smtp_settings[:user_name], smtp_settings[:password],
smtp_settings[:authentication]) do |smtp|
smtp.sendmail(mail.encoded, sender, destinations)
end
First, we create the new SMTP object and then we enable StartTLS if necessary (this allows you to send email through SMTP hosts that require SSL connections). Next we login to the SMTP server (with the start command) and call sendmail inside the block to do the actual delivery.
We’re done. That’s how an email gets sent in Rails. Next week we will create a quick monkey patch for ActionMailer based on the information that we learned from the trek.