ChainMail Logo

ChainMail

Coverage Status Ruby Build Status License: MIT RuboCop

ChainMail is a Ruby gem that ensures your transactional emails never fail by automatically switching between multiple email providers (SendGrid, Postmark, Mailgun, SES, etc.) when one fails to send. No more lost emails, no more manual intervention required.

Why ChainMail?

  • Zero Downtime: If one provider fails, emails automatically route to the next available provider
  • Easy Setup: Simple configuration with familiar Rails patterns
  • Multiple Providers: Built-in support for all major email services
  • Error Aggregation: Get detailed reports on any delivery issues

Installation

Add this line to your application's Gemfile:

gem 'chain_mail'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install chain_mail

Usage

Rails Setup

Add a configuration initializer at config/initializers/chain_mail.rb:

ChainMail.configure do |config|
  config.providers = [
    { send_grid:  { api_key: ENV["SENDGRID_API_KEY"] } },
    { mailgun:    { domain: ENV["MAILGUN_DOMAIN"], api_key: ENV["MAILGUN_API_KEY"] } },
    # Add more providers as needed
  ]
end

Set the delivery method in your environment config (e.g. config/environments/production.rb):

config.action_mailer.delivery_method = :chain_mail

Send an email using ActionMailer:

class UserMailer < ApplicationMailer
  def welcome_email(user)
    mail(
      to: user.email,
      from: '[email protected]',
      subject: 'Welcome!',
      body: 'Hello and welcome!'
    )
  end
end

Requirements

  • Ruby 3.0 or higher
  • Rails 6.0+ (for Rails integration)
  • Active email provider accounts (SendGrid, Postmark, etc.)

Architecture

  • Configuration: Set up providers and credentials in an initializer or before sending.
  • Delivery: Handles failover, input validation, and error aggregation.
  • Providers: Each adapter implements a standardized interface and error handling.

Supported Email Providers

ChainMail includes built-in support for the following email providers. Here's how to configure each one in your Rails initializer:

Amazon SES

ChainMail.configure do |config|
  config.providers = [
    { ses: {
        region: ENV["AWS_REGION"],
        access_key_id: ENV["AWS_ACCESS_KEY_ID"],
        secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"]
      } }
  ]
end

Brevo (formerly Sendinblue)

ChainMail.configure do |config|
  config.providers = [
    { brevo: {
        api_key: ENV["BREVO_API_KEY"],
        sandbox: ENV["BREVO_SANDBOX"] == "true" # optional
      } }
  ]
end

Mailgun

ChainMail.configure do |config|
  config.providers = [
    { mailgun: {
        domain: ENV["MAILGUN_DOMAIN"],
        api_key: ENV["MAILGUN_API_KEY"]
      } }
  ]
end

OneSignal

ChainMail.configure do |config|
  config.providers = [
    { one_signal: { api_key: ENV["ONESIGNAL_API_KEY"] } }
  ]
end

Postmark

ChainMail.configure do |config|
  config.providers = [
    { postmark: { api_key: ENV["POSTMARK_API_KEY"] } }
  ]
end

SendGrid

ChainMail.configure do |config|
  config.providers = [
    { send_grid: { api_key: ENV["SENDGRID_API_KEY"] } }
  ]
end

SendPulse

ChainMail.configure do |config|
  config.providers = [
    { send_pulse: {
        client_id: ENV["SENDPULSE_CLIENT_ID"],
        client_secret: ENV["SENDPULSE_CLIENT_SECRET"]
      } }
  ]
end

Multiple Providers by Priority (send_grid -> mailgun -> ses)

ChainMail.configure do |config|
  config.providers = [
    { send_grid: { api_key: ENV["SENDGRID_API_KEY"]} },
    { mailgun: {
        domain: ENV["MAILGUN_DOMAIN"],
        api_key: ENV["MAILGUN_API_KEY"]
      } },
    { ses: {
        region: ENV["AWS_REGION"],
        access_key_id: ENV["AWS_ACCESS_KEY_ID"],
        secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"]
      } }
  ]
end

Note: Always store API keys and credentials securely using environment variables. Providers are tried in the order listed - the first available provider will handle the email delivery.

Provider Priorities & Dynamic Configuration

  • Providers are tried in the order listed in config.providers.
  • You can register/unregister adapters at runtime:
ChainMail.register_provider(:custom, CustomProviderClass)
ChainMail.unregister_provider(:send_grid)
  • You can update provider priorities or credentials dynamically:
ChainMail.config.providers = [
  { custom: { api_key: "CUSTOM_API_KEY" } },
  { mailgun: { domain: "MAILGUN_DOMAIN", api_key: "MAILGUN_API_KEY" } }
]
  • API keys and credentials should be stored securely, e.g. using environment variables.

Development

Check out the repo and start developing!

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/taltas/chain_mail.