Skip to main content

23 Sending emails with Mandrill

Episode 14 · July 7, 2014

Advanced techniques on sending emails over SMTP with local views and Mandrill's API with remote templates

Gems Emails


Just a heads up! Mandrill now requires a paid Mailchimp account to use it. There are plenty of other options that have free tiers work similarly such as Sendgrid, Postmark, MailJet, MailGun, and so on.

Resources

Transcripts

I want to talk about sending emails in your rails application, and how we can do that using smtp and Mandrill's API. Mandrill is a service that allows you to send emails very easily, and you basically just register and connect it to your rails application, and that's as simple as it needs to be. You'll do that over smtp, but there's also an API that allows you to create templates inside of Mandrill, and you can use those to design your application in a way that allows your customers if you're consulting or your marketing people or whoever to go and change the emails at any point in time. You will just give them some variables and the content that they could put in the email if they choose to, and that allows you a lot of flexibility and you don't have to update your rails application every time you want to change an email. So let's dive into this.

I think the first thing we want to do is open up our application and go into the config/environments/development.rb, and the blind that we want to add here at the bottom is the exact same as the one that devise told us to do before. I haven't actually done it, so I'm going to edit it now.

config.action_mailer.default_url_options = { hoset: "localhost:3000" }

That is the domain url that I type in my browser when I'm doing development. So when you want to send an email with your rails application, you pass in to action mailer the objects that you want to use, and action mailer handles this by defining who is going to receive the email, who's sending the email, what the subject is, and what the view of the email should look like. So the view of the email is either in html or a text template that you render, and that gets sent back to the smtp server and it's handled appropriately. The reason why we have to set this url host option here, is because there is no url that this came in on for sending an email. Because sending an email is a separate transaction, you want to define what's going on here separately so that you can use this in other areas. So for example, if you have a background script that runs every night at midnight, and that needs to send an email out for some reason, there's no request that has come into your website that you can count on, so what you end up doing is you configure the mailer to globally have this feature, and then as that is necessary when an email is being sent, it will use that. You're not really inside of your rails application to the same extent as you are when you're in a controller, the action mailer stuff is significantly separate. It's important to understand the separation of action_mailer and the rest of your rails application, because I've seen a lot of confusion come out of that. Your action mailer is really just a tool to integrate with a remote mail system that you can use in your application to send information out to users. Of course, any time that you modify your config environment, initializers or your Gemfile, you want to make sure you restart your rails server. So we might expect that if we went into our application now and click login, and click "Forgot your password" and you typed in your email address here, when you click: "Send instructions", you get a notice saying that it's sent the email and you would maybe expect that you would receive an email. Well the reason it doesn't actually send one is because you haven't configured your action_mailer to actually connect to an smtp server to send the email across. And if you look at your rails logs, you can see that when it actually sent the email, it rendered it out in the terminal, and this tell you that the email was actually trying to be sent, which is good. However, it didn't correctly send, and it sort of failed silently, and that is something that you need to be aware of, that sometimes your emails will send, sometimes they won't, and you need to be very clear that this is working and this isn't working, or whatever. So you don't want ambiguity there when something is failing. The way that you can change that, is you can go back into the development.rb and you can tell it to raise delivery errors when a mailer can't send

config.action_mailer.raise_delivery_errors = true

In development, we don't really want it to fail when we're doing our first version of testing the emails, so if we've never had it work before, then it's good to see what things are going wrong, at a point where you have your application fully built and functional, and you trust that sending the emails works, then you can go back and set this to false, because in development you probably don't need to send yourself a whole bunch of test emails after you've tested to make sure that it's working. So if you want to do this, it's optional, but you'll need to also restart your rails server if you change this.

One other configuration option that I want to change real quick is if you go into config/initializers/devise.rb, there is a mail_sender option here that is basically the email address where your devise mails come from, and when it says: Please change me, it's probably a good idea to change it. So we're going to my gorails address, and if you are familiar with email formatting for email addresses, you can actually put an email in here, and then you can wrap your email address in <> symbols, so then your name will show up as well as the email correctly getting parsed.

After you've registered with Mandrill, you'll get taken to the homepage which gives you links to it's set up, smpt or the api version of Mandrill. We're going to look at the smtp version, and if we jump in there, we get presented with this, and it basically is saying: Here is where you will send the emails to, you will connect to it over a port 587, your username will be your email address, and your password will be any of the API keys that you create, so we're going to create an API key, and you just click the button, and it just creates an API key for you. so we can take this information now, and set it up inside of our rails application. Before we go back to our rails application, let's copy the API key to the clipboard so we can paste into our configuration as we write it. If we go back to MacVim, we can create a new file config/initializers/mail.rb If you want, you can mail separate the configuration out separately for development and production, and use two different mail servers if you'd like. I'm going to configure my application to use the same Mandrill account for both because I'm generally not going to be sending many in development, and I really want to make sure that the configuration works the same between the two.

config/initializers/mail.rb

ActionMailer::Base smtp_settings = {
    address: "smtp.mandrillapp.com",
    port: 587,
    enable_starttls_auto: true, 
    user_name: "[email protected]"
    password: <apikey>
    authentication: "login"
    ActionMailer::Base.delivery_method = :smtp 
    ActionMailer::Base.default charset: "utf-8"
}

These are all specific to smtp connections, so don't worry about them too much unless you're using a different service like SendGrid and it needs different configuration options. The last two options will allow us to send many of the utf-8 characters across, and it won't choke on them. Now we can restart our rails server to initialize all of the smtp configuration, and we can go back to our rails app, and click "Forgot your password", and I'm going to send a "forgot your password" email to [email protected], I've already created this account, as well as [email protected], which is my other email address, so if I send this password, reset instructions, we'll get the same message as before, and immediately you can see that I've got an email, and it has come from "Chris at Gorails", and the email address, so the name that we've displayed here properly, and we can click "Change my password to come back to our application", and follow the new password flow thing. So it has a reset token that it set's, and allows you to set your new password that you can do, and click "Change my password", and you're back into your application, logged in as that account. So if you'd like to customize the content of the email, that devise sends for reset your password, you can go into the app/views/devise/mailer folder, and see the password instructions, and this is where it generates the email content, but in order to get to this file, you need to make sure that you've run rails generate devise:views, and that will generate these files and make them available for you to customize.

Now, imagine that you want to add your own email that gets sent out when a new user is created, you like to see the user's that are signing up, kind of get notifications of that, and then be able to write a personal note to each person. So you want to set up an "after create" email that happens when a new user signs up. So let's go ahead and do that. We're going to generate a mailer called "AdminMailer" because this is an email that only goes out to admins in the site, so that will be you and pretty much nobody else for the moment. So we'll generate this mailer, and then we can open it up in our text editor, so we can go into the app/mailers/admin_mailer.rb, and because this inherits from ActionMailer::Base, this is the exact same class that we configured in our mail.rb, and you can see that here in all of these lines, and that means that we automatically have these settings applied to this mailer, so this mailer will be connected to Mandrill over smtp. So we can change the

default from: "[email protected]"

Because this is just a notification, and if we reply to the email, we don't need to do anything with it, so we'll leave it at that, and that way when you reply to the email, it just gets ignored, and because we're making a mailer to only ourselves, we can have

default to: "[email protected]"

This will be overridable in the individual methods, but we're not really going to worry about that. We can make a new email by just defining a method, and I'm going to call this one new_user

app/mailers/admin_mailer.rb

class AdminMailer < ActionMailer::Base
    default from: "[email protected]"
    default to: "[email protected]"

    def new_user(user)
        @user = user
        mail(subject: "New User: #{user.email})
    end 
end 

This is similar to a respond_to in a controller, where this will say: Ok, set up a mail, and we'll send it out, and this will automatically look in our app/views/admin_mailer folder for the same template named new_user.html.erb, so in here

app/views/admin_mailer/new_user.html.erb

<h1><%= @user.email %></h1>

The email will be very simple but it will be sent out when the user is created. So if we go into our user model, we can go add

app/models/user.rb

after_create :send_notification

def send_notification 
    AdminMailer.new_user(self).deliver 
end 

This will create the email, and then the deliver will actually send it is just initializing it, and the deliver will actually send it out. Now, in order to test this, I'm going to open up the rails console, and just delete the last user that I created which happens to be "[email protected]". Now, we can jump back into our rails application, and because I've deleted the account I was logged in as, I'm signed out, and this time, if I sign up, and I put in a password, I will be signed in, it will appear that nothing has changed to the user, but my email already has a new email, and you can see the email address of the person who signed up. That is a very quick way of integrating Mandrill with smtp and setting that up, and you have a fully transactional email system, built without any trouble at all. But to change the emails, we actually have to go in here, we have to edit this, we have to pass in new variables, and we have to do a bunch of work, just to make any changes in the emails. That might be ok, but for a lot of customers that you're building a site for, you're going to want to allow them to be able to change how the email works without having to hire you to put in some more time to make a change, and that's what we're going to talk about next with Mandrill API integration.

Mandrill provides a RESTful API that you can integrate with your application, so you can send a request over to Mandrill through an http post, for example, and that will be interpreted by Mandrill to take the data you give it, and send out an email. So you don't actually have to connect to Mandrill through your smtp, you can talk to it just like you would talk to another web sever. So you can make a POST request just like your users are making post requests on your site. Your website will do the same to Mandrill, and Mandrill will send out an email. So we're going to talk a little bit about this, and the way that you want to integrate this with rails, is through the Mandrill API rubygem, and it's hosted on BitBucket. Most of these seem to be hosted on GitHub, but this one just happens to be on BitBucket. There's not a ton of good documentation on it, but it's not very hard to use, so we'll be able to jump into that pretty quickly. Now you want to grab the line for your Gemfile and install that as normal. Run bundle install and restart our rails server.

The admin mailer that we have configured right now, uses action mailer's smtp integration with it, and that works really well. We can also use the same class to define methods to send emails over the API instead of using the smtp mail method that we are right now. So we can create a new method called new_book, and we'll pass in the book inside here, we can call Mandrill's API instead of the mail method. And that would do the same thing as sending an email across. So we actually want to do a few things before we get in too deep with the Mandrill API. And the first thing that we want to do is define a method in here

app/mailers/admin_mailer.rb

def mandrill_client 
    @mandrill_client ||= Mandrill::API.new MANDRILL_API_KEY 
end 

If you're not familiar with the nil guard, when you call this method, it will execute the code here on the right side and save it on the variable, and then the next time you call it, it will just return the results from the variable rather than executing the code again. So you can kind of cache things really quickly in memory. And we are wanting to cache the Mandrill API client, and the client will have the API key that we have before stored here, so we're actually going to name a constant called MANDRILL_API_KEY, and we'll pass that in. On or initializers/mail.rb, this is the API key that we had before, now we want to put it in two places. So it makes sense for us to delete this, and use a constant in both places, so we can have it in our initializer and our mailer. And we can take this and assign it at the top of the file. Since we set a constant like that, we're also going to want to restart our rails application to have that set globally across our app.

Inside of Mandrill, we can go into our Outbound menu and go down to templates, and here's where you can create these templates that you will use to render emails for us. So similar to the view templates that we have inside our application, like the new_user.html.erb, we can create a template on Mandrill that will store it remotely and then keep track of that and make it customizable. So the name of our template, we can call new_book, and we can add this template, and you can put in the html, and then text over here, as well as configure a little bit about the defaults for the "From Address" and who is it from, and the Subject. We can also override those from our application, and handle how that works. So imagine that we want to do the same notification, but with a book instead. If you're not familiar with MailChimp at all, they have these things called Merge tags, and they're essentially variables that you can put inside of your template that are replace dynamically. So we can name this "BOOK_NAME", and this variable denoted by the | will automatically get replaced by a variable that we pass across. We also want to define the "From Adress", and the name before we move on, because we don't need to specify this every single time in the mailer when we send out these emails. So if we publish this template, we will have it available in our application to talk to. And if we go back into our mailer, down in the *new_book method, we can begin defining access to that, and how we're going to replace the "book name" variable in our template with the content that we have. So we have the book, and if you're forgetting, the models/book.rb has the name column on it. It's not title, it's called name. So this is another case where annotate is really helpful, because you can easily look that up. In admin_mailer.rb, we want to define several variables that we're going to pass along to Mandrill that will determine who this gets sent to and everything. Now these defaults that we defined up here, are going to be ignored, because this is not talking over smtp, so everything in action mailer, is going to get ignored essentially because it's going to be handled by Mandrill itself. So the first thing we want to do, is we want to declare what template we are looking for, and the one that we created, is called new_book. And if you go over to Mandrill, you can see the template slug is called new_book, and they even have a helpful hint here, that you use the template slug to identify the template in the API or via smtp headers. So that is what the "new-book" string comes from, and we can also define a thing called template_content. I'm going to leave this empty because the template content that Mandrill provides is ok, but I'd prefer to use the merge tags instead, and we're going to use those and create that right now. So if we create the message, this is what defines the email itself, so we can say that this email goes to, and we can provide an array of people that it goes to, and you can put in the email, and I'm going to hardcode it as my email. If you want to make it dynamic, then you would pass in a user in here, and then instead of this, you would say: user.email, but because I want a hard-coded, I'm going to do that, because this is an admin notification. You can also add in your name in there if you want, you can leave it blank, it's not required, and that makes it just a little friendlier when you receive the message. The next option is the subject of the email, and just like before, we can say: "New book". The next option is called merge_vers, and this is the array of options that you can pass in that are variables that will be replaced by the string, so we have "book name", and we'll define that here to get replaced in the template. Now this is a little bit different, because when you're sending an email like this with smtp, you do it one by one, so you go talk to the server, and you say: Hey, send this out. And with the API, we can actually define a bunch of these at once. So imagine I want to send an email to "[email protected]", or "[email protected]", I could do that by just adding items into this to: field, and they would get sent. And the merge_vers are interesting because when you create these, you have to provide a recipient, and that will check the list of emails that you're sending to and see if it matches and then it will apply it to that email, so every email that you send out could have the same template, but different content. So it's really interesting that you can do that all in one request, and for this we'll want to have the same matching email address as before, and we can pass in the vars option which will be a bunch of variables that we would like to replace. We can save that and then close our recipient, and then we have a closing tag for the merge_vers, and we've defined all of the things that we need inside of our message variable:

Quick recap:

app/mailers/admin_mailer.rb

def new_book(book)
    template_name = "new-book"
    template_content = []
    message = {
        to: [(email: "[email protected]")],
        subject: "New Book: #{book.name}",
        merge_vars: [
            {rcpt: "[email protected]",
            vars: [
                {name: "BOOK_NAME", content: book.name}
                ]
            }
        ]
    }

    mandrill_client.messages.send_template template_name, template_content, message
end

If we save this, we can actually test this out with a book, and let's do that in our terminal, rather than integrating it with the model just yet. So we can grab a book, and we'll just grab the first book, which is: "How to win friends and influence people", and we can say

AdminMailer.new_book(book)

And we don't have to call deliver on this because deliver will initiate the smtp call but because we're doing that with the last line here, we're telling it to execute, we don't need to call .deliver on the end. So we can just simply run this, and we see that we run into a problem called "uninitialized constan Mandril:API", now this is because the Gemfile that we have needs to call an option called require: Mandrill, now they've named the gem "mandrill-api", but the module inside of it, is actually called "Mandrill", we can see that when we call Mandrill:API So by default, the gem will require Mandrill API, but since the name doesn't match you have to add the special option in there. It's one of those things that is not obvious, and because their documentation is a little lacking, it's kind of frustrating to work with a gem like this when it's not very smooth in some places, but generally it works really well, and we can restart our rails console, and run the same command. And we can say

AdminMailer.new_book(book)

And this time it will find the Mandrill API constant and send it over, so we get a NullMail response back because we didn't actually send a real action_mailer request. However, when we check our email, we should get a new email from Mandrill. If we take a look at this email, it's got the right name for the person to send it from, it has the right title, and if we look inside of the email, it's got the right title. So this is all working correctly, and now we can go into Mandrill, and just simply make changes. We want to say "New book:" in here as well, we can publish that, and then if we go back to our terminal, we can run the new book method again, send another email, and we immediately get a new email with the change, so we're able to make changes to the email instantly, and the only thing this depends upon, is that you provide the variables across, so if you want to add a new variable, you'll have to do that, but to change the look or the layout or any of those things in the email, you can do that without having to touch a rails application.

Transcript written by Miguel

Discussion


Gravatar

Excellent tutorial. Congrats.


Gravatar

Hey Chris, would you recommend gem 'mandrill-rails' too? or is gem "madrill-api" better?

Gravatar

Hey Angelo! I believe the mandrill-rails gem only supports Mandrill's inbound email webhooks.

The primary goal of Mandrill::Rails is to make supporting Mandrill web hooks as easy and Rails-native as possible.

The mandrill-api gem should be for accessing their REST API which you can find here: https://mandrillapp.com/api...

Gravatar

Right! My bad hehe thanks!


Gravatar

Thank you. Very complete tutorial.
(I would use it in companion of the dotenv gem to avoid adding the the api-key on the configuration files and prevent committing it into the repository.)


Gravatar

Why I can not sign up on Mandrill, when I signed up , it just returned "Error 417"


Gravatar

Not sure if I am watching this wrong. I was trying to follow along adding a functioning mailer to an application I'm already working on. It seems like this tutorial begins in the middle of something else, with more features added than I was told in the video.

I was expecting the tutorial to go from being a basic app with no functioning mailer to being a basic app with a functioning mailer

Gravatar

Check out the handful of episodes just before this one to get the bigger picture.

Gravatar

Thanks for the timely reply. I've been at it this whole time. Trying to piece it together. I'll stick around :)


Gravatar

Just wondering what are benefits to use Mandrill over the simple mail() method that you mentioned apart from the flexibility of templates manipulation out of code?

Gravatar

Basically, you need to run a mail server somewhere in either case. Mandrill serves as that whether you just use it for SMTP or you hit their API instead to send the emails without using SMTP.

Gravatar

It also seems Mandrill does much of the legwork with ISPs and Email services to give you a better chance of your mail actually getting delivered — and not flagged as spam.


Gravatar

Don't you usually put the ActionMailer smtp settings and everything else in the environment files?

Gravatar

these are Devise email templates right?

Gravatar

This isn't Devise, but Devise's emails work the same way. It uses your ActionMailer config so it will send them as well.

Gravatar

Yes, because usually I'll have a separate account between development (often disabled or a test account that's a free plan) and production.


Gravatar

I want to pass in specific information from the form through the request parameter url. How can I do that. For example a user submits a project for bidding in a form, and the project has a project title, how do I pass the project's title to the mail sent?

Gravatar

You can pass in the object into the mailer as an argument. Just set that to an instance variable and render it in your mailer view. If you're using Mandrill templates, you can pass the values over as merge variables instead.

Gravatar

Hi Chris, that was helpful, using the merge tags with my Mandrill templates resolved it. Also, the "mandrill_mailer" gem has a helpful documentation for anyone how cares. Thanks.


Gravatar

To help out future rookies who will find this:
I don't know if the mandrill gem changed but following your steps, I could not get emails to send. Turns out in the Mandrill gem docs they say "The gem assumes your API key is stored as an environment variable called MANDRILL_APIKEY." http://help.mandrill.com/en...

So I figured out foreman, and how to load an .env file etc and then it started working.

Gravatar

I went through this whole process with Foreman, Unicorn, Figaro, and Resque. Is there anything you installed or did you might think I'm missing here?


Gravatar

Also, has anyone else seen a delay in sending emails from Mandrill to Gmail? I would see a delay of up to 15 mins sometimes. Usually it is around a minute. Checked Mandrill status, no problems there.

Gravatar

It depends on their SMTP system, I've seen the same delays at times. They might be queuing or batching emails behind the scenes or any multitude of things between them and GMail could be slow. I'm not quite sure.

Gravatar

I was seeing 5 minute+ delays and no shows - which is a problem for password resets! Chris has done the hard work so switching between providers isn't a chore. I'm testing another SMTP service and this is giving a better response time.


Gravatar

Thanks Chris, awesome as usual ;)


Gravatar

I'm trying to pass the multiple arguments into the example new_book method, in this case a book and a user, and I can't get this to work and I'm a little frustrated. It works fine with one argument, the book, but adding multiple arguments messes it up. The after_create call gets called BEFORE the book is put in the database, so how do I access the book information to pass it? I was thinking with some kind of params call, but it doesn't seem to be accessible. Any ideas are welcome!

Gravatar

I tried refactoring the new_user method as well, and the same thing happened. It goes through in the console, but nothing gets to the Mandrill servers BUT, the reset password does go through, so it doesn't seem to be an SMTP problem but something within the new_book method.

Gravatar

Ok, after all of this, it seems that in my particular app, in the AdminMailer.rb file, the “default from: 'xxxxx'" under ”Class AdminMailer < ActionMailer::Base" does not work when attempting to send a template designed in Mandrill. You need to specifically add a "from_email" call in the new_book(book) function for it work.

Also, in order to get the two arguments passed, I had to make things look like this:

Book.rb

after_create :send_email

def send_email
user = self.user
AdminMailer.new_book(self, user).deliver_now
end

AdminMailer.rb

def new_event(book, user)
Mandrill Info
end

Hope this helps someone out there.

Gravatar

Glad you got it working! As an aside, one great way to test all this is to manually create and test your emails from the Rails console. It can help you poke around and find bugs like this quicker than going through the browser.


Gravatar

I am getting <actionmailer::base::nullmail:0x007f91f5a2e5c0> error. Why is that??


Gravatar

user_name: is that the email you created the mandrill account, heroku, github, what account are you talking about. Do i make up and email address? Feeling very lost about what is going on with mailers.

Gravatar

When you register for Mandrill, you have to sign up with an email address. That's the email you will put into the user_name field so it knows which account is sending the emails.

Gravatar

Thank you, I was playing around with different emails and i got it working.
It's great to have a current resource.


Gravatar

How would you catch tests when using the Mandrill API and templates? At the moment I'm just skipping it using something like:

# MyMailer.rb
def new_book(book)
return if Rails.env.test?
...
end


Gravatar

I have a 10 day e-mail drip campaign set up in MailChimp. What's the best way to have new signups be automatically added to the list for this campagin? From what I'm reading it looks like you have to code the logic for automatic e-mails your self instead of just using mailchimp.. is this correct?

Gravatar

The Gibbon gem should let you do this. You can add someone to the list when they sign up. This is what I do with GoRails on registrations so that I can send the announcement emails out with Mailchimp. That *should* do the trick for you there.

Gravatar

Ok thanks! I'll check it out after lunch. I appreciate the guidance

Gravatar

Always glad to help! Let me know how it goes (and how lunch is). ;)

Gravatar

Hi Chris, I am reading gibbon gem but unsure on what it is exactly for? Would you describe how you are using it for gorails to illustrate?


Gravatar

Great vid, how would I get the CSS to work in the mailer.html.erb templates?

Want t to style the mailer.html.erb so that when someone get the email in their gmail or other it looks presentable - Tried styling in myself and with bootstrap but the styles don't make it with the content to the recipients email account.

Gravatar

Unfortunately you'll have to paste all the bootstrap styles inline into the email templates. Emails only work with limited CSS.

Gravatar

Nevermind - You need Inline style like this in template:<head><style> hr {color:sienna;} p {margin-left:20px;} body {background-image:url("images/back40.gif");} </style> </head>

Gravatar

late reply, but this can be helpful: http://foundation.zurb.com/...


Gravatar

Thank you very much Chris. Great video. Helped me setup up smtp for a class project.


Gravatar

Hello chris. I followed all the steps as you showed in this screen cast but unfortunately I am not receiving the emails. I neither setup the domain in Mandrill inbound option and all I get the response in Outbound is that Email Rejected Reason Unsigned. I don't know why. I registered to mandrill with my normal gmail account. Can you please tell me what I would be doing wrong?

Gravatar

It sounds like you need to verify your domain ownership. Check the note at the bottom here: https://mandrill.zendesk.co...


Gravatar

Thanks for the update @excid3:disqus! Yea.. wanted notify anyone else looking at this tutorial that Mandrill is a paid service after today's announcement. Perhaps a tutorial on how to set up with Amazon SES or just Gmail if the load is not heavy..? :)

Gravatar

That Would de great! A tutorial on either Amazon SES or Sendgrid would be highly useful!!


Gravatar

The others mail services that you provided, do they have ruby gems?


Gravatar

I'm getting uninitialized constant AdminMailer::MANDRIL_API_KEY on rspec test, how can i add the mailers to my rspec tests?


Login or create an account to join the conversation.