Dennis Hackethal’s Blog
My blog about philosophy, coding, 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.
Reply