Dennis Hackethal’s Blog

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

History of post ‘Sending Emails in Production using Rails, Heroku, and MailGun’

Versions are sorted from most recent to oldest with the original version at the bottom. Changes are highlighted relative to the next, i.e. older, version underneath. Only changed lines and their surrounding lines are shown, except for the original version, which is shown in full.

Revision 2 · · View this (the most recent) version (v3)

Fix markdown once more

@@ -116,7 +116,7 @@ This works because in this particular use case, the recipient's email is static.

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+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.


Revision 1 · · View this version (v2)

Fix markdown

@@ -1,123 +1,123 @@
# 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:

```shell
$ rails g mailer comment_mailer new_comment
```

This will output:

```shell
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:

```ruby
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:

```ruby
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:

```ruby
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`:

```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:

```ruby
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](https://devcenter.heroku.com/articles/managing-add-ons):

```shell
$ heroku addons:create mailgun:starter
```

Then, add the following to `production.rb`:

```ruby
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](https://devcenter.heroku.com/articles/mailgun#sending-emails-via-smtp), 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 ;-)).

Original · · View this version (v1)

# 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:

```shell
$ rails g mailer comment_mailer new_comment
```

This will output:

```shell
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:

```ruby
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:

```ruby
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:

```ruby
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`:

```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:

```ruby
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](https://devcenter.heroku.com/articles/managing-add-ons):

```shell
$ heroku addons:create mailgun:starter
```

Then, add the following to `production.rb`:

```ruby
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](https://devcenter.heroku.com/articles/mailgun#sending-emails-via-smtp), 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 ;-)).