Docs / Ruby on Rails

Send email in Ruby on Rails over SMTP

Rails sends email through Action Mailer, which needs one smtp_settings hash to talk to any SMTP relay. This guide configures Postwing as that relay and covers mailer classes, HTML and text views, attachments, and moving delivery off the request with deliver_later.

You can get them on the token management page. For security reasons, a token is shown only once — at the moment it is created.

SMTP connection settings

SettingValue
SMTP hostsmtp.postwing.app
Port587
EncryptionSTARTTLS (the connection is upgraded to TLS before login)
UsernameThe login of an SMTP token for your domain
PasswordThe password of that token — shown once, when the token is created
Every mode is also available on a high port: 8465 (SSL/TLS), 8587 (STARTTLS) and 8025 (plain). Many hosting providers and clouds block outbound 25, 465 and 587 — if the connection times out, switch to the matching high port.

Configure Action Mailer

Add the following to config/environments/production.rb (and to staging.rb, if you have one):

config/environments/production.rb
# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true   # do not fail silently

config.action_mailer.default_url_options = { host: "your-domain.com" }

config.action_mailer.smtp_settings = {
  address:              "smtp.postwing.app",
  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,
}

For implicit TLS on port 465, swap the three transport keys:

ruby
# Implicit TLS on port 465 instead of STARTTLS
port:                 465,
ssl:                  true,
enable_starttls_auto: false,

Keep the credentials in Rails' encrypted credentials store:

properties
# EDITOR=vim bin/rails credentials:edit
smtp:
  user: token-login@your-domain.com
  password: your-token-password
It defaults to false outside development, which means Action Mailer discards SMTP errors and a completely broken configuration looks identical to a working one. This is the single most common reason "Rails says it sent the email but nothing arrived".

Create a mailer

bash
bin/rails generate mailer OrderMailer confirmed
ruby
# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "Acme <noreply@your-domain.com>"
  layout "mailer"
end

# app/mailers/order_mailer.rb
class OrderMailer < ApplicationMailer
  def confirmed(order)
    @order = order
    mail(to: order.customer.email, subject: "Your order ##{order.id} is confirmed")
  end
end

Give every mailer both an HTML and a text view. Rails detects the pair by filename and builds a multipart/alternative message automatically — HTML with no text part is a spam signal:

ruby
<%# app/views/order_mailer/confirmed.html.erb %>
<h1>Order confirmed</h1>
<p>Thanks, <%= @order.customer.name %>. Your order ships tomorrow.</p>

<%# app/views/order_mailer/confirmed.text.erb %>
Order confirmed

Thanks, <%= @order.customer.name %>. Your order ships tomorrow.

Deliver the email

ruby
# Blocking — the request waits for the SMTP round trip.
OrderMailer.confirmed(@order).deliver_now

# Queued via Active Job — returns immediately, retried on failure.
OrderMailer.confirmed(@order).deliver_later

# Or after a delay
OrderMailer.confirmed(@order).deliver_later(wait: 10.minutes)

Prefer deliver_later in anything triggered by a request. deliver_now holds the response open for the whole SMTP round trip, and turns a momentary relay hiccup into a 500 for the user.

Send an email with an attachment

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

    # Inline, referenced from the view as attachments["logo.png"].url
    attachments.inline["logo.png"] = File.read(Rails.root.join("app/assets/images/logo.png"))

    mail(to: invoice.customer.email, subject: "Your invoice")
  end
end

Send to multiple recipients

Passing an array to to: puts every address in one To: header where recipients see each other. Loop instead:

ruby
# One message per recipient — nobody sees anybody else's address.
User.find_each do |user|
  ReportMailer.weekly(user).deliver_later
end
Attention!
When using BCC, CC - a separate email will be created for each recipient in the system and your limits will be used.

Test without sending real email

ruby
# config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener   # opens mail in the browser
# or simply
config.action_mailer.perform_deliveries = false

In tests Rails collects everything in ActionMailer::Base.deliveries:

ruby
# test/mailers/order_mailer_test.rb
test "confirmation is delivered" do
  assert_emails 1 do
    OrderMailer.confirmed(orders(:one)).deliver_now
  end

  mail = ActionMailer::Base.deliveries.last
  assert_equal ["customer@example.com"], mail.to
  assert_match "confirmed", mail.subject
end

Troubleshooting

ErrorCause and fix
No error, no emailraise_delivery_errors is false. Set it to true to see the real failure.
Net::SMTPAuthenticationError: 535Wrong user_name or password.
Net::OpenTimeout Outbound port blocked. Use 8587 (STARTTLS) or 8465 (implicit TLS).
OpenSSL::SSL::SSLError: wrong version numberssl: true on port 587, or STARTTLS on 465. Match the mode to the port.
Links point at localhost:3000default_url_options[:host] is unset for that environment.
Net::SMTPFatalError: 550 The from domain is not verified, or the token does not cover it.

Frequently asked questions

Why does Rails report the email was sent when nothing arrives?

raise_delivery_errors defaults to false outside development, so Action Mailer swallows every SMTP error. Set config.action_mailer.raise_delivery_errors = true and the real failure — authentication, timeout, rejected sender — surfaces immediately.

Should I use enable_starttls_auto or ssl in smtp_settings?

On port 587 use enable_starttls_auto: true, which upgrades the plain connection to TLS before login. On port 465 use ssl: true with enable_starttls_auto: false, because that port is encrypted from the first byte. Mixing them causes a handshake failure.

Why do I get 'Net::SMTPAuthenticationError: 535'?

The user_name or password is wrong. user_name must be the full login of an SMTP token for your domain, and password the value shown once when the token was created. Store both in Rails credentials rather than in the environment file.

What is the difference between deliver_now and deliver_later in Rails?

deliver_now opens the SMTP connection inside the current request, so the user waits for it and a transient failure becomes a 500. deliver_later enqueues an Active Job that a worker delivers, which keeps the request fast and retries automatically. Use deliver_later everywhere except in scripts and tests.

Why do Rails email links point at localhost?

Mailer views have no request context, so *_url helpers fall back to whatever default_url_options says. Set config.action_mailer.default_url_options = { host: 'your-domain.com' } in each environment.

Why do Rails emails time out in production but work in development?

Many hosting providers block outbound ports 25, 465 and 587. Change the port in smtp_settings to 8587 for STARTTLS or 8465 for implicit TLS.

Next steps