Документация / Ruby on Rails

Отправка email в Ruby on Rails через SMTP

Rails отправляет почту через Action Mailer, которому достаточно одного хеша smtp_settings. Ниже — настройка fmailer, классы мейлеров, HTML- и текстовые вьюхи, вложения и вынос доставки из запроса через deliver_later.

Логин и пароль можно получить на странице управления токенами домена.

Параметры подключения

ПараметрЗначение
SMTP-хостsmtp.fmailer.ru
Порт587
ШифрованиеSTARTTLS (соединение переводится в TLS до авторизации)
ЛогинЛогин SMTP-токена вашего домена
ПарольПароль этого токена — показывается один раз, при создании
Каждый режим доступен и на высоком порту: 8465 (SSL/TLS), 8587 (STARTTLS) и 8025 (без шифрования). Многие хостинги и облака блокируют исходящие 25, 465 и 587 — если соединение отваливается по таймауту, используйте соответствующий высокий порт

Настройка Action Mailer

config/environments/production.rbruby
# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true   # не проглатывать ошибки

config.action_mailer.default_url_options = { host: "example.ru" }

config.action_mailer.smtp_settings = {
  address:              "smtp.fmailer.ru",
  port:                 587,
  user_name:            Rails.application.credentials.dig(:smtp, :user),
  password:             Rails.application.credentials.dig(:smtp, :password),
  authentication:       :plain,
  enable_starttls_auto: true,
  open_timeout:         10,
  read_timeout:         10,
}

Для порта 465 поменяйте три параметра транспорта:

ruby
# Порт 465 — шифрование сразу, без STARTTLS
port:                 465,
ssl:                  true,
enable_starttls_auto: false,

Доступы держите в зашифрованных credentials Rails:

properties
# EDITOR=vim bin/rails credentials:edit
smtp:
  user: <логин токена>
  password: <пароль токена>
Вне окружения development этот параметр равен false, то есть Action Mailer молча проглатывает ошибки SMTP, и полностью сломанная конфигурация выглядит как рабочая. Это главная причина жалоб «Rails пишет, что отправил, а письма нет».

Мейлер

bash
bin/rails generate mailer OrderMailer confirmed
ruby
# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "Магазин <noreply@mail.example.ru>"
  layout "mailer"
end

# app/mailers/order_mailer.rb
class OrderMailer < ApplicationMailer
  def confirmed(order)
    @order = order
    mail(to: order.customer.email, subject: "Подтверждение заказа №#{order.id}")
  end
end

Делайте для каждого мейлера две вьюхи — HTML и текстовую. Rails найдёт пару по имени файла и соберёт multipart/alternative сам. HTML без текстовой части почтовые фильтры считают признаком спама:

ruby
<%# app/views/order_mailer/confirmed.html.erb %>
<h1>Заказ принят</h1>
<p><%= @order.customer.name %>, доставка — завтра до 18:00.</p>

<%# app/views/order_mailer/confirmed.text.erb %>
Заказ принят

<%= @order.customer.name %>, доставка — завтра до 18:00.

Отправка

ruby
# Блокирует запрос на время обмена с SMTP.
OrderMailer.confirmed(@order).deliver_now

# Через Active Job — ответ уходит сразу, при сбое будет повтор.
OrderMailer.confirmed(@order).deliver_later

# Или с задержкой
OrderMailer.confirmed(@order).deliver_later(wait: 10.minutes)

Во всём, что вызывается из запроса, используйте deliver_later. deliver_now держит ответ открытым на всё время обмена с SMTP и превращает кратковременный сбой релея в ошибку 500 для пользователя.

Вложения

ruby
class InvoiceMailer < ApplicationMailer
  def monthly(invoice)
    attachments["invoice.pdf"] = File.read(invoice.path)

    # Картинка в теле письма — в вьюхе как attachments["logo.png"].url
    attachments.inline["logo.png"] = File.read(Rails.root.join("app/assets/images/logo.png"))

    mail(to: invoice.customer.email, subject: "Счёт на оплату")
  end
end

Несколько получателей

Массив в to: кладёт все адреса в один заголовок To:, где получатели видят друг друга. Отправляйте циклом:

ruby
# Отдельное письмо каждому — никто не видит чужих адресов.
User.find_each do |user|
  ReportMailer.weekly(user).deliver_later
end
При использовании CC и BCC на каждого получателя внутри системы создаётся отдельное письмо, и лимиты расходуются соответственно.

Проверка без реальной отправки

ruby
# config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener   # открывает письма в браузере
# или просто
config.action_mailer.perform_deliveries = false
ruby
# test/mailers/order_mailer_test.rb
test "письмо о заказе отправляется" do
  assert_emails 1 do
    OrderMailer.confirmed(orders(:one)).deliver_now
  end

  mail = ActionMailer::Base.deliveries.last
  assert_equal ["client@example.ru"], mail.to
end

Разбор ошибок

ОшибкаПричина и решение
Ошибок нет, писем тожеraise_delivery_errors равен false. Включите его.
Net::SMTPAuthenticationError: 535Неверные user_name или password.
Net::OpenTimeout Порт заблокирован. Используйте 8587 или 8465.
OpenSSL::SSL::SSLError: wrong version numberssl: true на порту 587 или STARTTLS на 465.
Ссылки ведут на localhost:3000 Не задан default_url_options[:host] для этого окружения.
Net::SMTPFatalError: 550Домен отправителя не подтверждён.

Частые вопросы

Почему Rails сообщает об успешной отправке, но письмо не приходит?

Вне окружения development параметр raise_delivery_errors по умолчанию равен false, и Action Mailer проглатывает все ошибки SMTP. Поставьте config.action_mailer.raise_delivery_errors = true — и настоящая причина (авторизация, таймаут, отклонённый отправитель) станет видна сразу.

Что использовать — enable_starttls_auto или ssl?

На порту 587 нужен enable_starttls_auto: true — соединение открывается незашифрованным и переводится в TLS до авторизации. На порту 465 нужен ssl: true вместе с enable_starttls_auto: false, потому что там шифрование начинается с первого байта. Смешение этих вариантов приводит к ошибке рукопожатия.

Что означает Net::SMTPAuthenticationError: 535?

Неверные user_name или password. В user_name нужен полный логин SMTP-токена домена, в password — значение, показанное один раз при создании токена. Храните оба в Rails credentials, а не в файле окружения.

Чем deliver_now отличается от deliver_later?

deliver_now открывает SMTP-соединение прямо в текущем запросе: пользователь ждёт, а временный сбой превращается в ошибку 500. deliver_later ставит задачу Active Job, которую выполняет воркер, — запрос остаётся быстрым, а неудачная отправка повторяется. Везде, кроме скриптов и тестов, используйте deliver_later.

Почему ссылки в письмах ведут на localhost?

У вьюх мейлера нет контекста запроса, поэтому хелперы *_url берут хост из default_url_options. Задайте config.action_mailer.default_url_options = { host: 'example.ru' } в каждом окружении.

Почему на продакшене таймаут, а в разработке всё работает?

Многие хостинги блокируют исходящие порты 25, 465 и 587. Поменяйте порт в smtp_settings на 8587 для STARTTLS или 8465 для шифрования с первого байта.

Что дальше