Answers · Keys & Services

How do I send transactional email from a Rails app in production?

Set RESEND_API_KEY and MAIL_FROM, and ActionMailer delivers through Resend in production while development keeps opening messages in a browser tab. The code side is already wired. The real work is verifying your sending domain and publishing SPF, DKIM and DMARC, without which the mail sends and lands in spam.

The two variables

MAIL_FROM has to be an address at a domain you verified with the provider. A from address at an unverified domain is rejected at send time, which at least fails loudly.

bin/kamal app exec 'bin/rails runner "puts ActionMailer::Base.delivery_method"'

Adding an email

Use the add-mailer skill rather than writing one from scratch. It produces the mailer method, both the HTML and text views in the shared mailer layout, and the spec, in the shape the existing mailers use. Copying app/mailers/sign_in_code_mailer.rb by hand works too and is the same result with more chances to miss the text part.

Send asynchronously:

SomeMailer.some_email(user).deliver_later

deliver_later enqueues through Solid Queue, which runs in the same container. No Redis, no separate worker process, and a slow provider does not slow down the request.

Inline styles only

The mailer layout uses inline styles because email clients strip stylesheets. That is also why the brand palette exists twice: 01-tokens.css is the source of truth and Rails.application.config.x.brand_colors mirrors it as Ruby strings for the three things that cannot read a stylesheet, of which email is one. A spec fails if the two disagree.

Test the text part

Every mailer ships HTML and text. A lot of clients render the text version, and it is the one nobody looks at. In development both open in the browser with a toggle, so checking takes ten seconds per mailer.

Then the DNS

This is where deliverability actually lives. SPF, DKIM and DMARC records at your DNS host, values supplied by the provider. An app with perfect mailer code and no DNS records has its sign-in emails filtered, and no amount of Ruby fixes it.

Related questions

How do I preview emails in development without sending them?

One Shot uses letter_opener in development, so every deliver_later opens the rendered email in a browser tab instead of sending it. No key and no account are needed. That is how you read a sign-in code locally, and it means a seed script cannot accidentally email a real address.

What DNS records do I need to send email from my domain?

Three: an SPF record authorizing your sending provider, DKIM keys the provider gives you, and a DMARC policy. Your email provider publishes the exact values; you add them at your DNS host. Missing any of the three is the reason a correctly configured app still has its sign-in emails filtered, and no code change fixes it.

Do I need API keys to run a Rails starter kit locally?

No. Billing, email, bot checking and error reporting each sit behind an adapter that returns a real client when its credential is present and a deterministic local fake when it is not. A fresh clone runs bin/setup and bin/dev with no keys, and checkout, sign-in and gated features all work end to end.