Sending mail smtp gmail, with attachment using Ruby on Rails: ActionMailer

Ruby on rails has a native class to send email. It may look easy but it can get confusing when you have to configure your rails app to manage this.
All the configuration must be in the environment.rb file or in any specific environment file ( development.rb, production.rb, test.rb), however I sugest using environmet.rb so you won’t have to repeat configuration.

All the information on configuration is at http://api.rubyonrails.org but here is an example using gmail.

config/environment.rb
config.action_mailer.delivery_method = :smtp

# I do care if the mailer can't send
config.action_mailer.raise_delivery_errors = true

# Include your app's configuration here:
config.action_mailer.smtp_settings = {
:address => "smtp.gmail.com",
:port => "587",
:domain => "my_app_domain", #Example: blog.local
:authentication => :plain,
:user_name => "my.gmail.account",
:password => "th1s1s4l33tpwd!"
}

That is all the configuration to send the mail, now you need to create your mailer.
$ script/generate mailer my_mailer

Write all the logic of this mailer on the “model” my_mailer.rb, here is an example that also sends an attachment, I asume that you have a user model or a person model, this model should have a field name and email:

app/models/my_mailer.rb
class MyMailer < ActionMailer::Base

def email(recipient)
recipients recipient.email
from "my.gmail.account@gmail.com"
subject "This is a test mail"
body :user => recipient # Im sending the recipient object as a local variable in a partial, but you will access it as an instance variable

@file_name = "#{RAILS_ROOT}/public/attachment.txt"
create_attachment
attachment :content_type => "text/plain",
:filename => "Some useless attachment",
:body => File.read(@file_name)
delete_attachment_file
end

protected
def create_attachment
attachment = File.new(@file_name, 'w')
attachment.puts "I am a useless text file"
attachment.puts " C(0_0)D "
attachment.close
end

def delete_attachment_file
File.delete(@file_name)
end
end

Now let’s make a view for the mail layout. This view will define the body or content of your mail.
So now you need to create a file named email.erb this is because our method that sends the mail is named email, so this basically works as the views for the controller actions. Create your file under views/my_mailer. Here is an example that uses the local variable user that I send from the email method.

app/views/my_mailer/email.erb

Hello . I have an attachment for you :)

Ok now you have almost everything done, but we still have to call our email method. This is done using a dynamic method deliver_my_method_name(param_1, param_2, …, param_n)

This is an example of this dynamic method called from the person model.

app/models/person.rb
class Person < ActiveRecord::Base
def send_email
MyMailer.deliver_email(self) # I am sending the person as a parameter.
end
end

One last thing, to send email using accounts that need tsl like gmail you need some libraries that will give you extra methods. What i did to solve this was to create a lib file in my Rails application under the lib directory ( I know this is obvious but i have to be specific :D ). This is the code of that file, I found this code googling and I just copied it into my app, it works for me and I hope it’ll work for you:

lib/smtp_tsl.rb
require "openssl"
require "net/smtp"

Net::SMTP.class_eval do
private
def do_start(helodomain, user, secret, authtype)
raise IOError, 'SMTP session already started' if @started
if RUBY_VERSION == "1.8.7"
check_auth_args user, secret if user or secret
else
check_auth_args user, secret, authtype if user or secret
end

sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
@socket = Net::InternetMessageIO.new(sock)
@socket.read_timeout = 60 #@read_timeout
#@socket.debug_output = STDERR #@debug_output

check_response(critical { recv_response() })
do_helo(helodomain)

if starttls
raise 'openssl library not installed' unless defined?(OpenSSL)
ssl = OpenSSL::SSL::SSLSocket.new(sock)
ssl.sync_close = true
ssl.connect
@socket = Net::InternetMessageIO.new(ssl)
@socket.read_timeout = 60 #@read_timeout
#@socket.debug_output = STDERR #@debug_output
do_helo(helodomain)
end

authenticate user, secret, authtype if user
@started = true
ensure
unless @started
# authentication failed, cancel connection.
@socket.close if not @started and @socket and not @socket.closed?
@socket = nil
end
end

def do_helo(helodomain)
begin
if @esmtp
ehlo helodomain
else
helo helodomain
end
rescue Net::ProtocolError
if @esmtp
@esmtp = false
@error_occured = false
retry
end
raise
end
end

def starttls
getok('STARTTLS') rescue return false
return true
end

def quit
begin
getok('QUIT')
rescue EOFError, OpenSSL::SSL::SSLError
end
end
end

Enjoy. Leave me a comment if you see anything funny, annoying, cool or just to say hello.
Recommend Me

About these ads

7 Comments

  1. trebla
    Posted April 3, 2009 at 12:43 pm | Permalink | Reply

    Hi Gusortiz,
    Excellent. This is exactly what I was looking for. One quick question though. The email output is just a single text line. I have tried to override it with “text/html” setting in environment.rb but it didn’t make any difference. What else I have to change to beautify the email? Thanks.

  2. Posted April 23, 2009 at 2:06 am | Permalink | Reply

    \o/ Thank you very much for this. I was going crazy(er) with the limited info in the APIs. Now my local app can actually send email! with attachments! And there was much rejoicing. :)

  3. conspirisi
    Posted November 24, 2009 at 8:40 pm | Permalink | Reply

    With rails 2.3.4 do you still need to do that stuff with the

    lib/smtp_tsl.rb

    • gusortiz
      Posted November 25, 2009 at 4:17 am | Permalink | Reply

      I’m not quite sure friend, this is an old post and I have not done this again. But this lib was (or is) necessary because of Ruby and not Rails it self. Ill check it out and I will make an update here as soon as I find out.
      Thanks for commenting.

      • Deepak
        Posted August 12, 2010 at 9:04 am | Permalink

        hello,
        Here nowere u have used Controller .Is it not needed for any action to perform through controller.I am new to this and was trying out with sending of emails.SO plz help me out.

      • gusortiz
        Posted August 12, 2010 at 2:52 pm | Permalink

        Yes it is required for any action to go trough controller, in this case you could have a create account action for your users and call the send mail method from a callback for example
        after_save :send_confirmation_email
        This is a very old post though, Rails has changed a lot since then so I suggest you to use the newest release of Rails and read this other post about the new action mailer, is a lot smoother.
        Let me know if I can help you any further

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: