> ## Documentation Index
> Fetch the complete documentation index at: https://jetemail.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Ruby

> Official JetEmail Ruby SDK for transactional email

# Ruby SDK

The **[jetemail](https://github.com/jetemail/jetemail-ruby)** gem is the official Ruby and Rails SDK for JetEmail transactional email (MIT license, **Ruby 2.6+**, HTTParty 0.21+).

Create a **transactional** API key in the dashboard: **Outbound** → **Keys** → Add API key. Use that key with the SDK.

<Note>Using Rails? The gem registers an ActionMailer delivery method automatically. See [Rails integration](#rails-integration) below.</Note>

## Installation

```bash theme={null}
gem install jetemail
```

Or add to your `Gemfile` and run `bundle install`:

```ruby theme={null}
gem "jetemail"
```

Gem on RubyGems: [jetemail](https://rubygems.org/gems/jetemail). Source on GitHub: [jetemail/jetemail-ruby](https://github.com/jetemail/jetemail-ruby).

## Quick start

```ruby theme={null}
require "jetemail"

JetEmail.configure do |config|
  config.api_key = "your-transactional-api-key"
end

result = JetEmail::Emails.send({
  from: "Your App <hello@yourdomain.com>",
  to: "user@example.com",
  subject: "Welcome!",
  html: "<h1>Welcome!</h1>"
})

puts result[:id]
```

Use a `from` address on a **verified domain** (see [Getting started](/docs/transactional-email/getting-started)).

## Configuration

The SDK is configured once at the module level; there is no client object to instantiate.

```ruby theme={null}
# Block form
JetEmail.configure do |config|
  config.api_key = "your-transactional-api-key"
end

# Or assign directly
JetEmail.api_key = ENV["JETEMAIL_API_KEY"]

# The key can also be a Proc, called on every request
JetEmail.api_key = -> { fetch_key_from_vault }
```

`JetEmail.config` is an alias for `JetEmail.configure`. The API key is read on every request, so it can be set (or changed) at any point before a call.

To point the SDK at a different base URL, set the `JETEMAIL_BASE_URL` environment variable before the gem is loaded (default `https://api.jetemail.com`). There is no `base_url` configuration option.

The SDK exposes three modules:

| Module | Description |
| - | - |
| `JetEmail::Emails` | Send a single email |
| `JetEmail::Batch` | Send up to 100 emails in one request |
| `JetEmail::Webhooks` | Manage webhooks and verify webhook signatures |

## `JetEmail::Emails.send(params)`

Send a single email. `params` is a plain Hash (symbol keys, snake\_case) using the same field names as the REST API.

| Field | Required | Description |
| - | - | - |
| `from` | ✓ | Sender, e.g. `"Name <email@domain.com>"` |
| `to` | ✓ | Recipient(s), string or array (max 50) |
| `subject` | ✓ | Subject line |
| `html` | \* | HTML body (need `html` and/or `text`) |
| `text` | \* | Plain text (need `html` and/or `text`) |
| `cc`, `bcc`, `reply_to` | | Recipients, string or array (max 50 each) |
| `headers` | | Custom headers as `{ "X-Header" => "value" }` |
| `attachments` | | Array of `{ filename: ..., data: base64 }` (max 40MB total) |
| `eu` | | `true` to restrict delivery to the EU region |

```ruby theme={null}
require "base64"

result = JetEmail::Emails.send({
  from: "App <noreply@example.com>",
  to: "user@example.com",
  subject: "Order Confirmation",
  html: "<p>Your order has been confirmed.</p>",
  text: "Your order has been confirmed.",
  cc: ["manager@example.com"],
  bcc: ["archive@example.com"],
  reply_to: "support@example.com",
  headers: {
    "X-Custom-Header" => "value"
  },
  attachments: [
    {
      filename: "receipt.pdf",
      data: Base64.strict_encode64(File.binread("/path/to/receipt.pdf"))
    }
  ]
})

puts result[:id]       # Message ID
puts result[:response] # Queue confirmation message
```

The Hash is serialized to JSON and sent to the API as-is, so any other field the [`POST /email`](/docs/api-reference/introduction) endpoint accepts can be included too.

### Responses

Every call returns the parsed JSON response as a Hash with **symbol** keys (`result[:id]`, not `result["id"]`).

Response shape for a single send: `{ id: String, response: String }`.

## `JetEmail::Batch.send(emails)`

Send up to **100** emails in one request. Each element uses the same fields as a single send. Returns `summary` (`total`, `successful`, `failed`) and `results` per message (success with `id`, or error with `error`).

```ruby theme={null}
result = JetEmail::Batch.send([
  {
    from: "App <noreply@example.com>",
    to: "user1@example.com",
    subject: "Hello User 1",
    html: "<p>Welcome!</p>"
  },
  {
    from: "App <noreply@example.com>",
    to: "user2@example.com",
    subject: "Hello User 2",
    html: "<p>Welcome!</p>"
  }
])

puts result[:summary][:successful]

result[:results].each do |item|
  if item[:status] == "success"
    puts item[:id]
  else
    puts item[:error]
  end
end
```

## Error handling

API errors (HTTP 4xx/5xx) raise a subclass of `JetEmail::Error`. Every error exposes `message`, `status_code` (HTTP status) and `headers` (response headers). Calling the API without an API key set, or receiving a non-JSON response, also raises a `JetEmail::Error`. Network and transport failures are not wrapped and surface as the underlying HTTParty/Net::HTTP exceptions.

| Status | Error class |
| - | - |
| 400, 401, 403, 422 | `JetEmail::Error::InvalidRequestError` |
| 404 | `JetEmail::Error::NotFoundError` |
| 429 | `JetEmail::Error::RateLimitError` (adds `retry_after`, seconds) |
| 500 | `JetEmail::Error::InternalServerError` |
| Other | `JetEmail::Error` |

`InvalidRequestError`, `NotFoundError` and `RateLimitError` inherit from `JetEmail::Error::ClientError`; `InternalServerError` inherits from `JetEmail::Error::ServerError`.

```ruby theme={null}
begin
  JetEmail::Emails.send({ ... })
rescue JetEmail::Error::RateLimitError => e
  puts "Rate limited, retry after #{e.retry_after}s"
rescue JetEmail::Error::InvalidRequestError => e
  puts "Invalid request: #{e.message} (#{e.status_code})"
rescue JetEmail::Error => e
  # Any other API error
  puts e.message
  puts e.status_code
  puts e.headers
end
```

Common status codes: **400** invalid request, **401** bad/missing API key, **500** server error.

## Webhook signature verification

The SDK includes a helper for verifying [webhook signatures](/docs/webhooks/security). It checks the HMAC-SHA256 signature and rejects events whose timestamp is older than the tolerance (default 300 seconds). It returns `true` on success and raises `JetEmail::Error` on failure.

```ruby theme={null}
begin
  JetEmail::Webhooks.verify(
    payload: request.raw_post,
    signature: request.headers["X-Webhook-Signature"],
    timestamp: request.headers["X-Webhook-Timestamp"],
    secret: ENV["JETEMAIL_WEBHOOK_SECRET"]
    # tolerance: 300 # optional, seconds
  )

  # Signature is valid
rescue JetEmail::Error => e
  # Invalid or expired signature
  head :unauthorized
end
```

### Managing webhooks

`JetEmail::Webhooks` also wraps the webhook management endpoints. Each method returns the parsed JSON response as a Hash with symbol keys.

| Method | Description |
| - | - |
| `JetEmail::Webhooks.list` | List all webhooks |
| `JetEmail::Webhooks.get(uuid)` | Get a single webhook |
| `JetEmail::Webhooks.create(params)` | Create a webhook (`name`, `url`, `events`) |
| `JetEmail::Webhooks.update(params)` | Update a webhook (`uuid` plus fields to change) |
| `JetEmail::Webhooks.remove(uuid)` | Delete a webhook |
| `JetEmail::Webhooks.query(params)` | Query webhook events, e.g. `{ uuid: ..., event_type: ... }` |
| `JetEmail::Webhooks.replay(params)` | Replay an event by `event_id` or `source_uid` |

```ruby theme={null}
webhook = JetEmail::Webhooks.create({
  name: "My Webhook",
  url: "https://example.com/webhook",
  events: ["outbound.delivered", "outbound.bounced"]
})

puts webhook[:uuid]
```

See [Webhook events](/docs/webhooks/events) for the list of event types.

## Rails integration

When Rails and ActionMailer are loaded, the gem registers a `:jetemail` delivery method. Set your API key in an initializer and select the delivery method per environment:

```ruby theme={null}
# config/initializers/jetemail.rb
JetEmail.configure do |config|
  config.api_key = ENV["JETEMAIL_API_KEY"]
end

# config/environments/production.rb
config.action_mailer.delivery_method = :jetemail
```

Then use ActionMailer as normal:

```ruby theme={null}
class UserMailer < ApplicationMailer
  def welcome(user)
    mail(
      from: "App <hello@yourdomain.com>",
      to: user.email,
      subject: "Welcome!"
    )
  end
end
```

The delivery method maps `from`, `to`, `cc`, `bcc`, `reply_to`, `subject`, text/HTML parts, custom headers and attachments to a single `JetEmail::Emails.send` call, and sets the message's `message_id` to the returned JetEmail `id`.

***

For additional examples, see the **[README on GitHub](https://github.com/jetemail/jetemail-ruby)**. REST details: [API reference](/docs/api-reference/introduction).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.