Overview

Transactional email over a REST API: one endpoint to send, domain verification in one screen, and delivery logs that say what happened.

emails.sh sends transactional email over HTTPS. You POST a from address, a recipient, a subject, and a body to https://emails.sh/v1/emails, and you get back an id you can ask about later. That is the whole product surface for most people, and everything else here exists to support it.

A key made in the dashboard works immediately against onboarding@emails.sh, so you can send a real email before you have touched DNS. Sending from your own address takes one more step: add the domain, publish six DNS records, and verification finishes on its own.

The whole integration
curl -X POST https://emails.sh/v1/emails \
  -H "Authorization: Bearer $EMAILSSH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <hello@acme.com>",
    "to": ["ada@example.com"],
    "subject": "Your receipt from Acme",
    "html": "<p>Thanks for your order. Your receipt is attached.</p>",
    "text": "Thanks for your order. Your receipt is attached."
  }'
200 OK
{
  "id": "em_01J9X8Q2K7Y4RN3M",
  "status": "queued"
}

Where to start

What the API covers

POST /v1/emails

Send one email.

POST /v1/emails/batch

Send up to 100 in one request.

GET /v1/emails/:id

Status and delivery events for one email.

PATCH /v1/emails/:id

{ send_at } moves a booked send to a new time. It keeps its id. 409 too_late_to_reschedule once it has gone.

GET /v1/emails

The delivery log. limit defaults to 25 and tops out at 100.

POST /v1/emails/:id/cancel

Call off an email booked with send_at, before it goes.

DELETE /v1/messages/scheduled/:id

The same cancel, by the older spelling. Still works.

GET /v1/domains

Domains, with the records a pending one still needs.

POST /v1/domains

{ domain } returns every DNS record to publish.

GET /v1/domains/:id

One domain, with the records it still needs if it is pending.

POST /v1/domains/:id/verify

Check the records now and report which are missing.

PATCH /v1/domains/:id

{ tracking_host } sets the hostname in front of tracked links. null goes back to the shared one.

DELETE /v1/domains/:id

Remove a domain. DELETE /v1/domains?id= is the older spelling and still works.

Beyond those: /v1/api-keys to make and revoke keys, /v1/webhooks to receive delivery events, /v1/audiences for the small non-transactional case, and an inbound side at /v1/messages for replies to the mail you send. The full list is in the API reference.