# 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](/docs/domains/add-a-domain/), for example `acme.com`.
- An [API key](/docs/developers/api-keys/). A Sending Only key restricted to your domain is enough.
- Until your workspace has [production access](/docs/workspaces/production-access/), you can only send to the account emails of workspace members.

## Install the gem

```bash
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:

```bash
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:

```ruby title="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:

```ruby title="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:

```ruby title="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](/docs/smtp/settings/).

## Receive webhooks

[Create a webhook](/docs/webhooks/set-up/) that points to `https://your-app.com/webhooks/emailit`. Add a route and a controller that verifies the signature against the raw body:

```ruby title="config/routes.rb"
post "/webhooks/emailit", to: "emailit_webhooks#create"
```

```ruby title="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](/docs/webhooks/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](/docs/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](/docs/developers/api-keys/).

## Next steps

  - [Send email with the API](/docs/email-api/send-email/): Attachments, scheduling and tracking.
  - [Templates](/docs/templates/): Design emails in Emailit and send them by alias.
  - [Webhook event types](/docs/webhooks/event-types/): Every event and its payload.
  - [SMTP troubleshooting](/docs/smtp/troubleshooting/): Fix authentication and connection errors.

---
Source: https://emailit.com/docs/frameworks/rails/
