Courrier

API-powered email delivery for Ruby apps. With support for Mailgun, Postmark, Resend and more

Installation

Add the gem:

bundle add courrier

Generate the configuration file:

bin/rails generate courrier:install

This creates config/initializers/courrier.rb for configuring email providers and default settings.

Usage

Generate a new email:

bin/rails generate courrier:email Order
class OrderEmail < Courrier::Email
  def subject = "Here is your order!"

  def text
    <<~TEXT
      text body here
    TEXT
  end

  def html
    <<~HTML
      html body here
    HTML
  end
end

# OrderEmail.deliver to: "recipient@railsdesigner.com"

Configuration

Courrier uses a configuration system with three levels (from lowest to highest priority):

  1. Global configuration
Courrier.configure do |config|
  config.email = {
    provider: "postmark",
    api_key: "xyz"
  }

  config.from = "devs@railsdesigner.com"
  config.default_url_options = { host: "railsdesigner.com" }

  # Provider-specific configuration
  config.providers.loops.transactional_id = "default-template"
  config.providers.mailgun.domain = "notifications.railsdesigner.com"
end
  1. Email class defaults
class OrderEmail < Courrier::Email
  configure from: "orders@railsdesigner.com",
            cc: "records@railsdesigner.com",
            provider: "mailgun"
end
  1. Instance options
OrderEmail.deliver to: "recipient@railsdesigner.com",\
                   from: "shop@railsdesigner.com",\
                   provider: "sendgrid",\
                   api_key: "sk_a1b1c3"

Provider and API key settings can be overridden using environment variables (COURRIER_PROVIDER and COURRIER_API_KEY) for both global configuration and email class defaults.

Custom attributes

Besides the standard email attributes (from, to, reply_to, etc.), you can pass any additional attributes that will be available in your email templates:

OrderEmail.deliver to: "recipient@railsdesigner.com",\
                   download_url: downloads_path(token: "token")

These custom attributes are accessible directly in your email class:

def text
  <<~TEXT
    #{download_url}
  TEXT
end

Result object

When sending an email through Courrier, a Result object is returned that provides information about the delivery attempt. This object offers a simple interface to check the status and access response data.

Available methods

Method Return Type Description
success? Boolean Returns true if the API request was successful
response Net::HTTP::Response The raw HTTP response from the email provider
data Hash Parsed JSON response body from the provider
error Exception Contains any error that occurred during delivery

Example

delivery = OrderEmail.deliver(to: "recipient@example.com")

if delivery.success?
  puts "Email sent successfully!"
  puts "Provider response: #{delivery.data}"
else
  puts "Failed to send email: #{delivery.error}"
end

Providers

Courrier supports these transactional email providers:

More Features

Additional functionality to help with development and testing:

Background jobs (Rails only)

Use deliver_later to enqueue delivering using Rails’ ActiveJob. You can set
various ActiveJob-supported options in the email class, like so: enqueue queue: "emails", wait: 5.minutes.

  • queue, enqueue the email on the specified queue;
  • wait, enqueue the email to be delivered with a delay;
  • wait_until, enqueue the email to be delivered at (after) a specific date/time;
  • priority, enqueues the email with the specified priority.

Inbox (Rails only)

You can preview your emails in the inbox:

config.provider = "inbox"

# And add to your routes:
mount Courrier::Engine => "/courrier"

If you want to automatically open every email in your default browser:

config.provider = "inbox"
config.inbox.auto_open = true

Emails are automatically cleared with bin/rails tmp:clear, or manually with bin/rails courrier:clear.

Layout support

Wrap your email content using layouts:

class OrderEmail < Courrier::Email
 layout text: "%{content}\n\nThanks for your order!",
        html: "<div>\n%{content}\n</div>"
end

Using a method:

class OrderEmail < Courrier::Email
  layout html: :html_layout

  def html_layout
    <<~HTML
      <div style='font-family: ui-sans-serif, system-ui;'>
        %{content}
      </div>
    HTML
  end
end

Using a separate class:

class OrderEmail < Courrier::Email
  layout html: OrderLayout
end

class OrderLayout
  self.call
    <<~HTML
      <div style='font-family: ui-sans-serif, system-ui;'>
        %{content}
      </div>
    HTML
  end
end

Template files

Instead of defining text and html methods, you can create ERB template files:

class OrderEmail < Courrier::Email
  def subject = "Your order is ready!"
  # text and html content will be loaded from template files
end

Create template files alongside your email class:

  • app/emails/order_email.text.erb
  • app/emails/order_email.html.erb

Templates have access to all context options and instance variables:

<!-- app/emails/order_email.html.erb -->
<h1>Hello <%= name %>!</h1>
<p>Your order #<%= order_id %> is ready for pickup.</p>

Method definitions take precedence over template files when both exist. You can mix approaches. For example, define text in a method and use a template for the html:

class OrderEmail < Courrier::Email
  def subject = "Your order is ready!"

  def text = "Hello #{name}! Your order ##{order_id} is ready."

  # html will be loaded from app/emails/order_email.html.erb
end

Auto-generate text from HTML

Automatically generate plain text versions from your HTML emails:

config.auto_generate_text = true # Defaults to false

Email address helper

Compose email addresses with display names:

class SignupsController < ApplicationController
  def create
    recipient = email_with_name("devs@railsdesigner.com", "Rails Designer Devs")

    WelcomeEmail.deliver to: recipient
  end
end

In Plain Ruby Objects:

class Signup
  include Courrier::Email::Address

  def send_welcome_email(user)
    recipient = email_with_name(user.email_address, user.name)

    WelcomeEmail.deliver to: recipient
  end
end

Logger provider

Use Ruby’s built-in Logger for development and testing:

config.provider = "logger" # outputs emails to STDOUT
config.logger = custom_logger # optional: defaults to ::Logger.new($stdout)

Custom providers

Create your own provider by inheriting from Courrier::Email::Providers::Base:

class CustomProvider < Courrier::Email::Providers::Base
  ENDPOINT_URL = ""

  def body = ""

  def headers = ""
end

Then configure it:

config.provider = "CustomProvider"

Check the existing providers for implementation examples.

Newsletter subscriptions

Manage subscribers across popular email marketing platforms:

Courrier.configure do |config|
  config.subscriber = {
    provider: "buttondown",
    api_key: "your_api_key"
  }
end
# Add a subscriber
subscriber = Courrier::Subscriber.create "subscriber@example.com"

# Remove a subscriber
subscriber = Courrier::Subscriber.destroy "subscriber@example.com"

if subscriber.success?
  puts "Subscriber added!"
else
  puts "Error: #{subscriber.error}"
end

Supported providers

Provider-specific configuration:

config.subscriber = {
  provider: "mailchimp",
  api_key: "your_api_key",
  dc: "us19",
  list_id: "abc123"
}

Custom providers

Create custom providers by inheriting from Courrier::Subscriber::Base:

class CustomSubscriberProvider < Courrier::Subscriber::Base
  ENDPOINT_URL = "https://api.example.com/subscribers"

  def create(email)
    request(:post, ENDPOINT_URL, {"email" => email})
  end

  def destroy(email)
    request(:delete, "#{ENDPOINT_URL}/#{email}")
  end

  private

  def headers
    {
      "Authorization" => "Bearer #{@api_key}",
      "Content-Type" => "application/json"
    }
  end
end

Then configure it:

config.subscriber = {
  provider: CustomSubscriberProvider,
  api_key: "your_api_key"
}

See existing providers for more examples.

FAQ

Is this a replacement for ActionMailer?

Yes! While different in approach, Courrier can fully replace ActionMailer. It’s a modern alternative that focuses on API-based delivery. The main difference is in how emails are structured - Courrier uses a more straightforward, class-based approach.

Is this for Rails only?

Not at all! While Courrier has some Rails-specific goodies (like the inbox preview feature and generators), it works great with any Ruby application.

Can it send using SMTP?

No. Courrier is specifically built for API-based email delivery. If SMTP is needed, ActionMailer would be a better choices.

What’s the main benefit over ActionMailer?

Courrier offers a simpler, more modern approach to sending emails. Each email is a standalone class, configuration is straightforward (typically just only an API key is needed) and it packs few quality-of-life features (like the inbox feature and auto-generate text version).