API-powered email delivery and newsletter subscription management for Ruby apps
# Quick example
class OrderEmail < Courrier::Email
def subject = "Here is your order!"
def text = "Thanks for ordering"
def html = "<p>Thanks for ordering</p>"
end
OrderEmail.deliver to: "recipient@railsdesigner.com"
# Manage newsletter subscribers
Courrier::Subscriber.create "subscriber@example.com"
Courrier::Subscriber.destroy "subscriber@example.com"Sponsored By Rails Designer
Add the gem:
bundle add courrierTip
For Rails apps, use rails_courrier instead. It includes generators, ActiveJob support (deliver_later), inbox previews and more.
Configure Courrier in your app:
Courrier.configure do |config|
config.email = {
provider: "postmark",
api_key: "your-api-key"
}
config.from = "devs@example.com"
endclass 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"Courrier uses a configuration system with three levels (from lowest to highest priority):
- 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- Email class defaults
class OrderEmail < Courrier::Email
configure from: "orders@railsdesigner.com",
cc: "records@railsdesigner.com",
provider: "mailgun"
end- 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.
Email classes can define custom HTTP headers that are sent with every email:
class OrderEmail < Courrier::Email
headers list_unsubscribe_post: "List-Unsubscribe=One-Click"
def subject = "Rails Icons now supports SVG sprites!"
def text = # …
def html = # …
endUseful for adding provider-specific headers like List-Unsubscribe for Postmark, X-Mailer identifiers or custom metadata headers required.
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: "https://example.com/download?token=abc123"These custom attributes are accessible directly in your email class:
def text
<<~TEXT
#{download_url}
TEXT
endWhen 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.
| 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 |
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}"
endCourrier supports these transactional email providers:
Additional functionality to help with development and testing:
Wrap your email content using layouts:
class OrderEmail < Courrier::Email
layout text: "%{content}\n\nThanks for your order!",
html: "<div>\n%{content}\n</div>"
endUsing 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
endUsing 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
endInstead 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
endCreate template files alongside your email class (default path is courrier/emails):
courrier/emails/order_email.text.erbcourrier/emails/order_email.html.erb
Templates have access to all context options and instance variables:
<!-- courrier/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 courrier/emails/order_email.html.erb
endCourrier supports rendering markdown content to HTML when a markdown gem is available. Simply bundle any supported markdown gem (redcarpet, kramdown or commonmarker) and it will be used.
Define a markdown method in your email class:
class OrderEmail < Courrier::Email
def subject = "Your order is ready!"
def markdown
<<~MARKDOWN
# Hello #{name}!
Your order **##{order_id}** is ready for pickup.
## Order Details
- Item: #{item_name}
- Price: #{price}
MARKDOWN
end
endCreate markdown template files alongside your email class (default path is courrier/emails):
courrier/emails/order_email.md.erbcourrier/emails/order_email.markdown.erb
<!-- courrier/emails/order_email.md.erb -->
# Hello <%= name %>!
Your order **#<%= order_id %>** is ready for pickup.
## Order Details
- Item: <%= item_name %>
- Price: <%= price %>Method definitions take precedence over template files. You can mix approaches. For example, define text in a method and use a markdown template for HTML content.
Automatically generate plain text versions from your HTML emails:
config.auto_generate_text = true # defaults to falseCompose email addresses with display names:
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
endUse 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)Create your own provider by inheriting from Courrier::Email::Providers::Base:
class CustomProvider < Courrier::Email::Providers::Base
ENDPOINT_URL = ""
def body = ""
def headers = ""
endThen configure it:
config.provider = "CustomProvider"Check the existing providers for implementation examples.
Courrier provides Test and TestHelper for testing email delivery, similar to Action Mailer's testing API.
Access all delivered emails:
# Clear deliveries between tests
Courrier::Test.clear!
# Access all deliveries
Courrier::Test.deliveriesEach delivery record contains:
email_class; the email class nameto,from,reply_to,cc,bcc; email addressessubject; email subjectbody- Hash with:textand:htmlkeysheaders; custom headersprovider; provider usedresult; result object withsuccess?methodtimestamp; delivery time
Include the helper in your test class for assertions:
class OrderTest < Minitest::Test
include Courrier::TestHelper
def setup
Courrier::Test.clear!
end
def test_sends_confirmation_email
order = Order.create! product: "Widget", customer_email: "customer@example.com"
OrderEmail.deliver to: order.customer_email, order: order
assert_emails_delivered 1
assert_email_delivered to: "customer@example.com"
assert_email_delivered OrderEmail, subject: "Order"
end
def test_no_emails_when_order_cancelled
order = Order.create! product: "Widget", status: :cancelled
assert_no_emails_delivered
end
endAvailable assertions:
assert_emails_delivered(count); assert the number of emails deliveredassert_no_emails_delivered; assert no emails were deliveredassert_email_delivered(email_class, to:, from:, subject:, provider:); assert an email matching criteria was delivered
Hook into the email delivery lifecycle with before_deliver and after_deliver callbacks:
class OrderEmail < Courrier::Email
before_deliver do |email|
puts "Sending to #{email.options.to}" # access email options, abort delivery by returning false
end
after_deliver do |email, result|
puts "Delivered: #{result.success?}" # access email and delivery result
end
endCallbacks are isolated per class (subclasses don't inherit parent callbacks).
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- Beehiiv - requires
publication_id - Buttondown
- Kit (formerly ConvertKit) - requires
form_id - Loops
- Mailchimp - requires
dcandlist_id - MailerLite
Provider-specific configuration:
config.subscriber = {
provider: "mailchimp",
api_key: "your_api_key",
dc: "us19",
list_id: "abc123"
}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
endThen configure it:
config.subscriber = {
provider: CustomSubscriberProvider,
api_key: "your_api_key"
}See existing providers for more examples.
Not at all! Courrier works with any Ruby application. For Rails apps, use rails_courrier.
No. Courrier is specifically built for API-based email delivery.
This project uses Standard for formatting Ruby code. Please make sure to run rake before submitting pull requests.
Courrier is released under the MIT License.
