Notes on using ActionMailer
1. Mailer setup for localhost/development
#
Use gem 'letter_opener'
. This way you can see how emails look when they are delivered.
# config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true
On top of that, gem 'letter_opener_web'
provides a beautiful UI for emails in your tmp folder 🤩
If you still have an error missing host to link to
, you can add this:
# app/config/development.rb
require "active_support/core_ext/integer/time"
+ Rails.application.routes.default_url_options[:host] = 'localhost:3000'
Rails.application.configure do
2. Mailer setup for test
/staging
#
You might want to test real email delivery in PR apps / staging. You should be sure that you do not deliver these test emails to real people! The easiest solution would be to use an AWS sandbox domain. This way the emails will be delivered only to users from your domain. In the below example we do not want to get out of the sandbox:
3. Mailer setup for production
#
Check out my post: Sending emails in production with Amazon SES
4. Sending emails #
Generate a mailer:
rails g mailer post post_created
Always use deliver_later
, not deliver_now
:
# app/controllers/posts_controller.rb
PostMailer.with(user: current_user, post: @post).post_created.deliver_later
You can pass multiple params and attachments:
# app/mailers/post_mailer.rb
class PostMailer < ApplicationMailer
def post_created
@user = params[:user]
@post = params[:post]
@greeting = "Hi"
# attach from assets
attachments['logo.png'] = File.read('app/assets/images/logo.png')
# attach from /public folder
attachments.inline['logo.png'] = File.read("#{Rails.root}/public/images/logo.png")
# attach an ical event
attachments['calendar-event.ics'] = { mime_type: 'application/ics', content: icalendar.to_ical }
# attach an ActiveStorage file
file = @user.avatar
attachments['avatar.png'] = { mime_type: file.blob.content_type, content: file.blob.download }
# mail options
mail(
from: "Yaroslav <hello@superails.com>",
to: email_address_with_name(User.first.email, User.first.full_name),
cc: User.all.pluck(:email),
bcc: "secret@superails.com",
subject: "New post created"
)
end
end
Rendering attachments in the email html:
# app/views/post_mailer/post_created.html.erb
<%= asset_url('/images/games/game-card-backgrounds/football.png', host: 'https://superails.com') %>
<%= image_tag attachments['logo.png'].url, style: 'max-width: 16em;' %>
<%= image_tag attachments['avatar.png'].url, alt: 'My Photo', width: 100 %>
<h1>Post#post_created</h1>
<%= @user.email %> create <%= @post.title %>
Styling emails with CSS is tricky: you can’t be sure that different email clients (outlook/gmail) render the CSS in the same way.
I usually style emails with plain CSS, not a CSS framework.
5. Preview emails #
Best way for previewing your email html without actually sending it to an inbox:
# test/mailers/previews/post_mailer_preview.rb
class PostMailerPreview < ActionMailer::Preview
# Preview this email at http://localhost:3000/rails/mailers/post_mailer/post_created
def post_created
PostMailer.with(user: User.first, post: Post.first).post_created
end
end
6. Writing tests #
Minitest example:
# test/mailers/post_mailer_test.rb
require "test_helper"
class PostMailerTest < ActionMailer::TestCase
test "post_created" do
mail = PostMailer.post_created
assert_equal "Post created", mail.subject
assert_equal ["to@example.org"], mail.to
assert_equal ["from@example.com"], mail.from
assert_match "Hi", mail.body.encoded
end
end
Rspec example:
# main/spec/mailers/game_spec.rb
require 'rails_helper'
RSpec.describe PostMailer, type: :mailer do
let(:user) { create(:user) }
let(:post) { create(:post) }
describe 'reminder' do
let(:mail) { PostMailer.with(user:, game:).post_created }
it 'renders the headers' do
expect(mail.subject).to match('vs')
expect(mail.to).to eq([user.email])
expect(mail.from).to eq(['hello@superails.com'])
end
it 'renders the body' do
expect(mail.body.encoded).to match('Post created')
expect(mail.body.encoded).to match('logo')
expect(mail.body.encoded).to match('avatar')
end
end
end
7. Updating default layout #
# app/mailers/application_mailer.rb
default from: 'Yaro <hello@corsego.com>'
# app/views/layouts/mailer.html.erb
<body>
<%= yield %>
+ Regards, Yaroslav Shmarov
+ <br>
+ hello@superails.com
</body>
</html>
That’s it! 🎉🥳🍾
Did you like this article? Did it save you some time?