Tutorial
Send email with Ruby on Rails
Deliver Action Mailer email through Emailit with the emailit gem or SMTP settings, call the API directly, and verify Emailit webhooks in Rails.
This guide shows how to send Rails mail through Emailit. You can use the emailit gem’s Action Mailer delivery method, which sends through the API, or point Action Mailer’s SMTP settings at the Emailit relay. It also covers calling the API directly and verifying webhooks.
Prerequisites
- Ruby 3.0 or later and a Rails app.
- A verified sending domain, for example
acme.com. - An API key. A Sending Only key restricted to your domain is enough.
- Until your workspace has production access, you can only send to the account emails of workspace members.
Install the gem
bundle add emailitThe gem has no runtime dependencies beyond Ruby’s standard library.
Configure your API key
Store the key in your environment or in Rails credentials:
bin/rails credentials:editemailit:
api_key: secret_••••••••••••••••••••••••••••••••
webhook_secret: whsec_••••••••The examples below read Rails.application.credentials.dig(:emailit, :api_key). If you use environment variables, read ENV.fetch("EMAILIT_API_KEY") instead.
Send with Action Mailer
When the gem detects Rails, it registers an :emailit delivery method. Turn it on per environment:
config.action_mailer.delivery_method = :emailit
config.action_mailer.emailit_settings = {
api_key: Rails.application.credentials.dig(:emailit, :api_key)
}Your mailers don’t change. The from address must be on a verified sending domain:
class UserMailer < ApplicationMailer
default from: "Acme <hello@acme.com>"
def welcome(user)
@user = user
mail(to: @user.email, subject: "Welcome to Acme")
end
endUserMailer.welcome(user).deliver_laterThe delivery method converts each message to an API request, including from, to, cc, bcc, reply_to, the subject, HTML and text parts, and attachments.
Call the API directly
For features Action Mailer doesn’t model, such as stored templates or scheduled sends, use the client:
client = Emailit::EmailitClient.new(Rails.application.credentials.dig(:emailit, :api_key))
email = client.emails.send(
from: "Acme <hello@acme.com>",
to: user.email,
template: "welcome",
variables: { first_name: user.first_name },
scheduled_at: "tomorrow at 9am"
)
email.id # => "em_…"Errors are raised as typed exceptions: Emailit::AuthenticationError (401), Emailit::RateLimitError (429), Emailit::UnprocessableEntityError (422) and Emailit::ApiError for anything else.
Send with SMTP instead
To use plain SMTP, configure Action Mailer’s SMTP delivery:
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtp.emailit.com",
port: 587,
user_name: "emailit",
password: Rails.application.credentials.dig(:emailit, :api_key),
authentication: :plain,
enable_starttls_auto: true
}Mailers and deliver_later work the same way. If your host blocks port 587, use 2525 or 2587. See SMTP settings.
Receive webhooks
Create a webhook that points to https://your-app.com/webhooks/emailit. Add a route and a controller that verifies the signature against the raw body:
post "/webhooks/emailit", to: "emailit_webhooks#create"class EmailitWebhooksController < ActionController::API
def create
payload = request.raw_post
signature = request.headers["X-Emailit-Signature"].to_s
timestamp = request.headers["X-Emailit-Timestamp"].to_s
return head :unauthorized unless valid_signature?(payload, signature, timestamp)
# The body is a JSON array of up to 100 events.
JSON.parse(payload).each do |event|
case event["type"]
when "email.bounced", "email.complained"
address = event.dig("data", "object", "to")
# Stop emailing this address.
end
end
head :ok
end
private
def valid_signature?(payload, signature, timestamp)
return false if signature.empty? || timestamp.empty?
return false if (Time.now.to_i - timestamp.to_i).abs > 300
secret = Rails.application.credentials.dig(:emailit, :webhook_secret)
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{payload}")
ActiveSupport::SecurityUtils.secure_compare(expected, signature)
end
endInheriting from ActionController::API skips CSRF protection, which Emailit can’t satisfy. Return a 2xx within 30 seconds, and move slow work into Active Job. See Request signature.
Production tips
- Deliver later. Use
deliver_laterso web requests don’t wait for email, and throttle bulk jobs to stay under your sending limits (2 emails per second by default). - Keep development safe. Use
:letter_opener,:testor a separate staging key in development so you don’t email real users by accident. - Deduplicate webhooks. Store each
event_idyou process; failed deliveries are retried and can arrive more than once. - Rotate keys without downtime by deploying a new key before deleting the old one. See API keys.