March 16, 2026 . 14 min read
Email Delivery for Rails SaaS in 2026: Postmark, Resend, SES, and the Transactional Stack That Actually Works
Jeronim Morina
Founder, Omaship
Your SaaS sends emails. Password resets, welcome sequences, invoice receipts, team invitations, usage alerts. If those emails land in spam -- or do not arrive at all -- your product feels broken. Here is how to set up transactional email delivery in Rails 8 so it works on day one and scales to your first 10,000 customers.
Email delivery sounds simple until you try it. SMTP credentials, DNS records, deliverability reputation, bounce handling, suppression lists, DKIM alignment, DMARC policies -- the rabbit hole goes deep. Most SaaS founders either over-engineer it (setting up Amazon SES with custom bounce processing from day one) or under-engineer it (sending from Gmail SMTP and wondering why password resets hit spam).
This guide gives you the middle path: a production-ready email stack that takes 15 minutes to set up, delivers reliably from the start, and grows with your product.
The three types of email your SaaS sends
Before choosing a provider, understand what you are sending. SaaS email falls into three categories, and conflating them is the number one deliverability mistake founders make:
- Transactional email -- triggered by a user action. Password resets, email confirmations, invoice receipts, team invitations. These must arrive within seconds. Deliverability is critical. If a password reset lands in spam, your user cannot log in.
- Product notifications -- triggered by system events. Usage alerts, weekly digests, trial expiration warnings, feature announcements. These should arrive promptly but a 5-minute delay is acceptable. Unsubscribe links are mandatory.
- Marketing email -- sent in bulk to segments of your user base. Newsletters, product launches, drip campaigns. Deliverability matters but these emails are subject to stricter spam filtering. Always sent through a separate IP or subdomain.
The critical rule: never mix transactional and marketing email on the same sending domain or IP address. Marketing email attracts spam complaints. Even a small complaint rate (0.1%) can tank your sender reputation, which then drags down your transactional deliverability. A user who cannot reset their password because your newsletter got flagged is a user you have lost.
Provider comparison: Postmark vs Resend vs Amazon SES vs SendGrid
Here is the honest breakdown of every provider a Rails SaaS founder should consider in 2026:
Postmark
Best for: Transactional email, especially for SaaS products that care about deliverability above all else.
Postmark is opinionated. They only handle transactional email (they added a separate "broadcast" stream for marketing, but they actively discourage bulk sending). Their infrastructure is optimized for inbox placement of time-sensitive messages. Average delivery time is under 10 seconds.
- Pricing: $15/month for 10,000 emails. Straightforward, no hidden fees.
- Rails integration: First-class. The
postmark-railsgem drops into Action Mailer with one line of configuration. Bounce and spam complaint webhooks included. - Deliverability: Best in class. They maintain a 98%+ inbox placement rate by refusing to let spammers on the platform.
- Downside: Not the cheapest at scale. If you send 100K+ emails per month, the cost adds up. No built-in marketing automation.
Resend
Best for: Developer-first teams who want a modern API and React Email templates.
Resend is the newcomer. Built by Zeno Rocha (creator of Dracula Theme), it positions itself as "email for developers." Clean API, good documentation, first-class webhook support. They use Amazon SES infrastructure underneath but add a better DX layer on top.
- Pricing: Free tier (100 emails/day), then $20/month for 50,000 emails.
- Rails integration: SMTP relay works with standard Action Mailer config. Also has a REST API if you prefer direct integration.
- Deliverability: Good but not Postmark-level. Shared infrastructure means your reputation is partially influenced by other senders.
- Downside: Younger platform. Less track record for high-volume senders. React Email templates are not useful in a Rails context.
Amazon SES
Best for: High-volume senders who want the lowest per-email cost and are willing to manage deliverability themselves.
SES is raw infrastructure. It sends email. That is it. You handle bounce processing, complaint feedback loops, suppression list management, and reputation monitoring yourself. The tradeoff is price: $0.10 per 1,000 emails. At scale, nothing beats it.
- Pricing: $0.10/1,000 emails. No monthly minimums. Pay only for what you send.
- Rails integration: The
aws-sdk-sesv2gem or SMTP relay. Both work with Action Mailer. - Deliverability: Depends entirely on you. SES gives you a dedicated IP option but you must warm it up yourself. New accounts start in a sandbox that limits sending.
- Downside: Operational overhead. Bounce handling is your problem. Reputation monitoring is your problem. If you do not process bounces, SES will suspend your account.
SendGrid (Twilio)
Best for: Teams that need both transactional and marketing email in one platform.
SendGrid was the default choice for years. It does everything -- transactional, marketing, templates, analytics. The acquisition by Twilio brought more resources but also more complexity and occasional reliability issues that made headlines.
- Pricing: Free tier (100 emails/day), paid plans start at $19.95/month for 50,000 emails.
- Rails integration: SMTP relay. Well-documented.
- Deliverability: Varies. Their shared IP pools have mixed reputations because they serve a wide range of senders.
- Downside: Dashboard UX has degraded since the Twilio acquisition. Support quality depends on your plan tier. Shared IPs mean your deliverability is influenced by neighbors.
The recommended stack
For a new Rails SaaS in 2026, here is what works:
- Day 1 to 10K users: Postmark for all transactional email. $15/month. Do not overthink it. The deliverability alone is worth 10x the price compared to debugging why password resets are hitting spam on a cheaper provider.
- Marketing email: Keep it separate. Use a dedicated tool like Buttondown, ConvertKit, or Loops. Do not send newsletters through your transactional provider.
- At scale (50K+ emails/month): Consider moving high-volume notifications to Amazon SES while keeping critical transactional flows on Postmark. Two providers, two concerns, clean separation.
Setting up Action Mailer in Rails 8
Rails 8 ships with Action Mailer configured for SMTP delivery. Here is the minimal setup for Postmark:
# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtp.postmarkapp.com",
port: 587,
user_name: Rails.application.credentials.dig(:postmark, :api_token),
password: Rails.application.credentials.dig(:postmark, :api_token),
authentication: :plain,
enable_starttls: true
}
config.action_mailer.default_url_options = {
host: "yourdomain.com",
protocol: "https"
}
That is the entire email configuration. Rails credentials store the API token securely. The SMTP settings point to Postmark. Every mailer in your application now delivers through Postmark automatically.
For the postmark-rails gem (recommended for better error handling and webhook support):
# Gemfile
gem "postmark-rails"
# config/environments/production.rb
config.action_mailer.delivery_method = :postmark
config.action_mailer.postmark_settings = {
api_token: Rails.application.credentials.dig(:postmark, :api_token)
}
The gem gives you better error reporting, automatic retry on transient failures, and native support for Postmark message streams (separating transactional and broadcast email within Postmark).
DNS records: the part everyone forgets
Configuring SMTP credentials is step one. Without proper DNS records, your emails will land in spam regardless of which provider you use. Here are the three records every SaaS needs:
SPF (Sender Policy Framework)
SPF tells receiving mail servers which IP addresses are authorized to send email on behalf of your domain. Without it, anyone can forge your domain in the "From" field.
# DNS TXT record for yourdomain.com
v=spf1 include:spf.postmarkapp.com ~all
One record, one include. If you use multiple providers, add multiple includes. But keep the total DNS lookup count under 10 -- SPF has a hard limit.
DKIM (DomainKeys Identified Mail)
DKIM adds a cryptographic signature to every outgoing email. The receiving server verifies the signature against a public key published in your DNS. This proves the email was not tampered with in transit.
Every email provider generates a unique DKIM record for your domain. Postmark gives you a CNAME record to add. Resend and SES give you TXT records with the public key directly. Follow your provider's setup guide -- this is not something you configure manually.
DMARC (Domain-based Message Authentication, Reporting, and Conformance)
DMARC ties SPF and DKIM together and tells receiving servers what to do when authentication fails. Start with a monitoring-only policy:
# DNS TXT record for _dmarc.yourdomain.com
v=DMARC1; p=none; rua=mailto:[email protected]
p=none means "do not reject anything, just send me reports." After a few weeks of monitoring, move to p=quarantine and eventually p=reject. Gmail and Yahoo now require DMARC for bulk senders -- even if you only send transactional email, having a DMARC record improves your reputation.
Writing mailers that convert
A well-architected email system means nothing if the emails themselves are bad. Here are the patterns that work for SaaS:
Keep transactional emails plain
Password resets, email confirmations, and security alerts should be plain text or minimal HTML. No images. No marketing headers. No social media links in the footer. These emails have one job: deliver critical information. Anything else is a distraction that also triggers spam filters.
# app/mailers/passwords_mailer.rb
class PasswordsMailer < ApplicationMailer
def reset(user)
@user = user
@signed_id = @user.password_reset_tokens.create.signed_id(expires_in: 20.minutes)
mail to: @user.email, subject: "Reset your password"
end
end
The mailer is three lines. The view is a paragraph of text with a link. That is all a password reset email needs.
Use layouts for product notifications
Product notifications (trial expiration, usage alerts, weekly digests) benefit from a branded layout. But keep it minimal:
# app/views/layouts/mailer.html.erb
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: -apple-system, system-ui, sans-serif; line-height: 1.6; color: #1a1a1a; }
.container { max-width: 560px; margin: 0 auto; padding: 32px 16px; }
.footer { margin-top: 32px; padding-top: 16px; border-top: 1px solid #e5e5e5; font-size: 13px; color: #666; }
</style>
</head>
<body>
<div class="container">
<%= yield %>
<div class="footer">
<p>Sent by <%= @app_name %></p>
<%= link_to "Unsubscribe", unsubscribe_url(token: @unsubscribe_token) %>
</div>
</div>
</body>
</html>
Inline CSS only (many email clients strip <link> tags). No external images. No JavaScript. The simpler the HTML, the better it renders across Gmail, Apple Mail, Outlook, and every other client.
Always send both HTML and plain text
Action Mailer makes this trivial. Create both .html.erb and .text.erb templates for every mailer action. Rails automatically sends a multipart email with both versions. Plain text is not just an accessibility feature -- it is a deliverability signal. Emails with only HTML and no plain text alternative are more likely to be flagged as spam.
Handling bounces and complaints
Sending email is half the job. The other half is listening to what comes back. Two types of feedback matter:
- Hard bounces -- the email address does not exist. Stop sending immediately. If you keep sending to hard-bounced addresses, email providers will tank your reputation.
- Spam complaints -- the recipient clicked "Report spam." Suppress the address immediately and do not email them again. Even one complaint per thousand emails is considered high.
Every serious email provider offers webhook callbacks for bounces and complaints. Here is a minimal Rails implementation:
# app/controllers/webhooks/postmark_controller.rb
class Webhooks::PostmarkController < ApplicationController
skip_before_action :verify_authenticity_token
before_action :verify_webhook_token
def bounce
email = params[:Email]
user = User.find_by(email: email)
user&.update!(email_bounced: true, email_bounced_at: Time.current)
head :ok
end
def spam_complaint
email = params[:Email]
user = User.find_by(email: email)
user&.update!(email_suppressed: true, email_suppressed_at: Time.current)
head :ok
end
private
def verify_webhook_token
head :unauthorized unless request.headers["X-Postmark-Token"] == Rails.application.credentials.dig(:postmark, :webhook_token)
end
end
Then, in your mailers, check suppression before sending:
# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
before_action :check_suppression
private
def check_suppression
return unless @user&.email_suppressed? || @user&.email_bounced?
mail.perform_deliveries = false
end
end
This is the minimum viable bounce handling. It prevents you from repeatedly sending to addresses that will never receive your email, which protects your sender reputation.
Testing email delivery in development
Rails provides :test delivery method by default in the test environment, which stores emails in ActionMailer::Base.deliveries for assertions. For development, use Letter Opener:
# Gemfile (development only)
gem "letter_opener", group: :development
# config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true
Every email your app sends in development opens in a new browser tab instead of actually delivering. You can see exactly what the email looks like, inspect the headers, and verify the content -- all without configuring SMTP credentials or risking sending test emails to real addresses.
Background delivery with Solid Queue
Never send email synchronously in a web request. Even with a fast provider like Postmark, SMTP delivery takes 100-500ms. That delay compounds when you send multiple emails (welcome email + confirmation + admin notification). Use deliver_later to push email delivery to Solid Queue:
# Instead of this (blocks the request):
UserMailer.welcome(@user).deliver_now
# Do this (background job via Solid Queue):
UserMailer.welcome(@user).deliver_later
Rails 8 with Solid Queue makes this seamless. No Redis required. The email is serialized into the job queue and processed asynchronously. If delivery fails, Solid Queue retries with exponential backoff.
The five emails every SaaS needs on day one
Do not build an email system. Build five emails and a system that can send them reliably:
- Password reset -- non-negotiable. If a user cannot reset their password, they cannot use your product. Plain text, time-limited link, no fluff.
- Welcome email -- sent immediately after signup. One sentence about what to do next. Link to the dashboard. That is it. Not a five-paragraph essay about your company mission.
- Invoice receipt -- triggered by Stripe or Paddle webhook after successful payment. Include the amount, date, and a link to the invoice PDF. Required for B2B customers who need receipts for expense reports.
- Team invitation -- when a user invites a teammate. Include who invited them, which team, and a clear "Accept invitation" button. Time-limited for security.
- Trial expiration warning -- sent 3 days before trial ends. Not aggressive, not desperate. State the facts: "Your trial ends on [date]. Here is what happens next." Link to the upgrade page.
Everything else can wait. Drip campaigns, feature announcements, weekly digests -- build those when you have users who care about receiving them.
Common mistakes that kill deliverability
- Using your personal Gmail as the "from" address. Gmail's DMARC policy rejects emails claiming to be from @gmail.com that are not sent through Gmail servers. Use a custom domain from day one.
- Skipping DKIM and DMARC. Gmail and Yahoo started enforcing DMARC for bulk senders in 2024. Even if you are not a bulk sender, missing authentication records hurt your reputation with every provider.
- Sending marketing and transactional email from the same domain. One spam complaint from a newsletter can suppress your password reset emails. Use
[email protected]for transactional and[email protected]for everything else. - Not handling bounces. Hard bounces accumulate. If more than 2% of your emails bounce, providers start throttling or blocking your domain. Process bounce webhooks and suppress invalid addresses immediately.
- Over-designing transactional emails. Heavy HTML, embedded images, and tracking pixels in password reset emails are spam signals. Keep critical emails minimal.
How Omaship handles email
Omaship ships with Action Mailer configured for production delivery. The password reset flow uses the Rails 8 built-in authentication mailer. Team invitation mailers are included with the multitenancy setup. All mailers use deliver_later through Solid Queue by default.
You bring your own SMTP credentials (Postmark, Resend, SES -- any provider that speaks SMTP). Add the credentials to your Rails encrypted credentials file, set the SMTP host and port, and every mailer in your application starts delivering through your chosen provider. No gem installation, no initializer configuration beyond what Rails already provides.
The CI/CD pipeline includes a mailer smoke test that verifies all mailer actions render without errors. Preview classes are included for development so you can iterate on email templates in the browser without triggering actual sends.
Ship your SaaS with email that works from day one.
Omaship includes Action Mailer configured for production, password reset flows, team invitations, and Solid Queue background delivery. Add your SMTP credentials and start sending.
Start building