Skip to content
Docs

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.

Updated Oct 1, 2026

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

Terminal
bundle add emailit

The gem has no runtime dependencies beyond Ruby’s standard library.

Configure your API key

Store the key in your environment or in Rails credentials:

Terminal
bin/rails credentials:edit
YAML
emailit:
  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/environments/production.rb
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:

app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  default from: "Acme <hello@acme.com>"

  def welcome(user)
    @user = user
    mail(to: @user.email, subject: "Welcome to Acme")
  end
end
Ruby
UserMailer.welcome(user).deliver_later

The 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:

Ruby
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/environments/production.rb
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:

config/routes.rb
post "/webhooks/emailit", to: "emailit_webhooks#create"
app/controllers/emailit_webhooks_controller.rb
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
end

Inheriting 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_later so 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, :test or a separate staging key in development so you don’t email real users by accident.
  • Deduplicate webhooks. Store each event_id you 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.

Next steps

Attachments, scheduling and tracking.
Design emails in Emailit and send them by alias.
Every event and its payload.
Fix authentication and connection errors.

Was this page helpful?

Thanks for the feedback.

Thanks, we read every message.