Dennis Hackethal’s Blog

My blog about coding, philosophy, and anything else that interests me.

Sending Emails in Production using Rails, Heroku, and MailGun

Use case: You want to receive emails whenever something specific happens on your website.

I wanted to receive an email whenever somebody commented on my blog. It’s easy to do with Rails and Heroku.

Let me walk you through what I did:

$ rails g mailer comment_mailer new_comment

This will output:

create  app/mailers/comment_mailer.rb
invoke  erb
create    app/views/comment_mailer
create    app/views/comment_mailer/new_comment.text.erb
create    app/views/comment_mailer/new_comment.html.erb
invoke  test_unit
create    test/mailers/comment_mailer_test.rb
create    test/mailers/previews/comment_mailer_preview.rb

In other words, it creates the mailer, the action, and the templates for you. Then, in my comments controller, I added:

after_action :send_notification_email, only: :create

private
def send_notification_email
  begin
    CommentMailer.with(comment: @comment).new_comment.deliver_now
  rescue Exception => e
    puts "Caught exception sending email: #{e}"
  end
end

Note three things:

  • The method assumes that the controller’s create method will assign a @comment instance variable (e.g. @comment = Comment.create(comment_params))
  • It delivers immediately using deliver_now. That way, I don’t need to set up a job-processing backend in production.
  • It wraps the delivery in a begin/rescue statement so that the user doesn’t see any issues if sending an email fails. You may be able to achieve the same outcome by setting config.action_mailer.raise_delivery_errors = false in your environment file (e.g. development.rb), but I believe that that only causes the suppression of errors upon the actual sending of your email over the wire, not if there are any errors in, say, the email template. I may be wrong about that.

My ApplicationMailer looks like this:

class ApplicationMailer < ActionMailer::Base
  default from: 'foo@example.com', to: 'bar@example.com'
  layout 'mailer'
end

Since neither from nor to ever change, we can just set them in the ApplicationMailer.

My CommentMailer looks like this:

class CommentMailer < ApplicationMailer
  helper :application, :posts # optional, if you need helper methods in the mailer's views

  def new_comment
    @comment = params[:comment]

    mail subject: 'New comment on blog!'
  end
end

params come from the call to with in the comments controller. For example, CommentMailer.with(comment: @comment) sets the :comment field on the params to @comment.

Now for the mailer’s templates. They’re basically the same as regular controller views. For example, you could write something like the following in app/views/comment_mailer/new_comment.html.erb:

<h1>New comment!</h1>
<p>By: <%= @comment.author %></p>
<p><%= @comment.body %></p>

And something similar in the plain-text file at app/views/foo_mailer/new_comment.text.erb.

If you’re rendering links in these templates—e.g., using <%= link_to 'Post', @comment.post %>—you’ll need to set a host in your environment files so that Rails knows how to generate the href attribute for those links:

config.action_mailer.default_url_options = {
  host: '...' # e.g. 'localhost:3000' in development.rb or 'blog.example.com' in production.rb
}

Now we need to get all this working in production. Assuming you’re running on Heroku, add the MailGun add-on. You can do so either by going to the “Resources” tab in your app, or using the command line:

$ heroku addons:create mailgun:starter

Then, add the following to production.rb:

config.action_mailer.smtp_settings = {
  :port           => ENV['MAILGUN_SMTP_PORT'],
  :address        => ENV['MAILGUN_SMTP_SERVER'],
  :user_name      => ENV['MAILGUN_SMTP_LOGIN'],
  :password       => ENV['MAILGUN_SMTP_PASSWORD'],
  :domain         => 'blog.example.com', # or just yourapp.herokuapp.com
  :authentication => :plain # there may be more secure options available depending on your use case!
}

This codeblock is from the MailGun docs, with minor modifications. Note that you do not need to set those environment variables yourself.

If you were to try deploying this and triggering an email in production, you’d get an error message, because MailGun expects whitelisted recipient email addresses for sandbox domains. To add the recipient—in this case bar@example.com—click on the add-on in Heroku under “Resources” and you will see a column on the right with the heading “Authorized Recipients.” Add your recipient’s email address and you should be good to go.

This works because in this particular use case, the recipient’s email is static. If you need to send emails to programmatically determined recipients, I suppose you’ll somehow need to tell MailGun that you don’t need a sandbox.

If you’re running into problems, inspect your server logs by running $ heroku logs --tail and triggering an email. You should see exceptions in your logs, and if everything goes well, you’ll see a line that looks something like this:

Rendered comment_mailer/new_comment.html.erb within layouts/mailer (Duration: 0.5ms | Allocations: 265)

In development, you’ll see the whole email template rendered in your server logs.

Have any questions or feedback? Comment below (and you’ll trigger an email to me ;-)).


What people are saying

Hey Dennis, hope this email finds you well. It’s great to know that I can send you a direct email any time I want by commenting on your blog. Very interesting how a software engineer can’t figure out SSL! Although I guess you wouldn’t notice, seeing as how nobody but you reads these posts.

Connor | 4 months ago

What are your thoughts?

Markdown supported. cmd + enter to submit. You are responsible for what you write.