# Send emails with Ruby

> Send email from Ruby or Rails with the official sendping gem — zero runtime dependencies, plain-hash params, and typed errors.

The official [`sendping`](https://rubygems.org/gems/sendping) gem is the fastest way to send email from Ruby. It has **zero runtime dependencies** — only `net/http`, `json`, `openssl`, `uri`, `cgi` and `time` from the standard library — so it drops into a Rails app, a Sinatra service, or a bare script without pulling anything else in.

One thing to know up front: the gem is configured at the module level (`SendPing.api_key = ...`) rather than by constructing a client object, and every resource is a module method — `SendPing::Emails.send`, `SendPing::Contacts.create`, and so on. Note the capitalisation: the constant is `SendPing`, with a lowercase `b`.

## Prerequisites

1. A **verified domain** for your `from` address. ([guide](https://www.sendping.co/docs/domains/managing))
2. An **API key** (`mb_...`), read from `ENV["SENDPING_API_KEY"]` or Rails credentials. ([Authentication](https://www.sendping.co/docs/authentication))
3. **Ruby 2.7 or newer** — that is the floor declared by the gemspec (`required_ruby_version`).

## Install

```sh
gem install sendping
```

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

**Gemfile**

```ruby
gem "sendping", "~> 5.1"
```

## Configure

Set the key once at boot. `SendPing.api_key = ...` on its own is enough; the `configure` block is the same assignment with room for the optional settings.

**config/initializers/sendping.rb**

```ruby
require "sendping"

SendPing.configure do |config|
  config.api_key = ENV.fetch("SENDPING_API_KEY")

  # Optional:
  # config.base_url    = "https://www.sendping.co/api" # override the API host
  # config.timeout     = 10 # seconds per attempt (default 30; 0 disables)
  # config.max_retries = 3  # 429/503 retry budget (default 2)
end
```

> **Note:** Outside Rails, put the same lines wherever your process boots. Configuration is process-global and there is no client object to thread through your code, so `SendPing::Emails.send` works from anywhere once the key is set. Call it before setting the key and the gem raises a `SendPing::Error` whose `name` is `missing_api_key` — it fails locally rather than spending a request, which is also why that one error has a `nil` `#status_code`.

## Send an email

Params are a plain hash with snake_case keys, passed through as JSON. `delivered@test.sendping.co` is SendPing's mailbox simulator — the send is accepted, produces a real email object and a delivery event, and never reaches a provider, so it is safe to send to while you wire things up. Do not point a send at `example.com`: those recipients are suppressed and the call comes back `422 validation_error` having sent nothing.

**send.rb**

```ruby
require "sendping"

SendPing.api_key = ENV.fetch("SENDPING_API_KEY")

begin
  sent = SendPing::Emails.send({
    from: "Acme <hello@yourdomain.com>",
    to: ["delivered@test.sendping.co"],
    subject: "Hello from Ruby",
    html: "<p>Sent with the official Ruby gem 💎</p>"
  })

  puts "Sent email #{sent['id']}"
rescue SendPing::Error => e
  warn "SendPing #{e.status_code} #{e.name}: #{e.message}"
end
```

## Handling the response

A successful call returns the parsed JSON body — a `Hash` with **string** keys, so the id is `sent["id"]` and not `sent[:id]`. Keep it to [retrieve the email](https://www.sendping.co/docs/api/emails-get) later or to correlate [webhook](https://www.sendping.co/docs/webhooks/overview) events.

```json
{
  "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794"
}
```

Any non-2xx answer raises `SendPing::Error`, which carries the API's `{ statusCode, name, message }` envelope as `#status_code`, `#name` and `#message`. Branch on `#name` together with `#status_code`, never on `#message` — message text is scrubbed server-side and is not a stable contract. Plan and quota rejections additionally populate `#limit` and reputation gates populate `#reputation`; both are `nil` on an ordinary error, and `#body` is the whole parsed body so a field newer than your gem version is still reachable.

```ruby
begin
  SendPing::Emails.send(params)
rescue SendPing::Error => e
  case e.name
  when "validation_error"
    # 422 — a bad field (and 403 on a raw request with no User-Agent).
  when "daily_quota_exceeded", "monthly_quota_exceeded", "plan_limit_reached"
    if (cap = e.limit)
      warn "#{cap['kind']} cap hit: #{cap['used']}/#{cap['limit']}"
    end
  when "missing_api_key", "invalid_api_key", "restricted_api_key"
    # 401/403 — the key is absent, wrong, or lacks the scope for this route.
  else
    raise
  end
end
```

## From a Rails controller or job

With the initializer above in place there is nothing else to wire up. Send from a background job so a slow API call never blocks the request.

**app/jobs/welcome_email_job.rb**

```ruby
class WelcomeEmailJob < ApplicationJob
  queue_as :mailers

  def perform(user)
    sent = SendPing::Emails.send(
      {
        from: "Acme <hello@yourdomain.com>",
        to: [user.email],
        subject: "Welcome to Acme",
        html: "<p>Hi #{CGI.escapeHTML(user.first_name.to_s)}, thanks for signing up.</p>"
      },
      { idempotency_key: "welcome-#{user.id}" }
    )

    user.update!(welcome_email_id: sent["id"])
  end
end
```

The second argument is an options hash. `idempotency_key` is honoured only on the two send endpoints — `SendPing::Emails.send`, and `SendPing::Batch.send` or its `SendPing::Emails.batch` alias — a retry with the same key replays the original response instead of sending twice. It must be 1–255 characters (`SendPing::Client::IDEMPOTENCY_KEY_MAX_LENGTH`); the server answers anything else with `400 invalid_idempotency_key`.

> **Note:** The gem sets a `User-Agent` on every request (`sendping-ruby/5.1.1`), which the API requires on every route — a request without one is rejected with `403 validation_error` before it is even authenticated. It also retries `429` and `503` automatically, honouring `Retry-After`, up to `SendPing.max_retries` times (default 2). Nothing else is retried — not a timeout, not a network error, not any other status — so a send is never silently duplicated.

> **Warning:** Keep the `mb_` API key server-side — in Rails credentials or an environment variable, never in client-side code or version control. Anyone with the key can send email as you.

## Next steps

- Send up to 100 messages in one request with `SendPing::Batch.send([...])`. Batch items reject `attachments` and `scheduled_at` — send those one at a time.
- See the full [Send Email API](https://www.sendping.co/docs/api/emails-send) reference for every body field (`cc`, `bcc`, `reply_to`, `attachments`, `scheduled_at`).
- Explore [the SDKs reference](https://www.sendping.co/docs/resources/sdks) for the other resources the gem exposes — contacts, segments, campaigns, templates, automations, webhooks and events.
- Verify inbound webhooks with `SendPing::Webhooks.verify(raw_body, headers, signing_secret)` — pass `request.raw_post`, never re-serialized JSON. See [Webhooks](https://www.sendping.co/docs/webhooks/overview).
- Prefer no gem at all? [Send with Ruby over raw HTTP](https://www.sendping.co/docs/send-with/ruby) uses only `net/http`.
