sendapi.email API reference
API reference

SendAPI.email API

Send transactional email from your own domain, follow each email from queued to delivered, and keep your suppression list in sync.

Base URLhttps://sendapi.email/v1
AuthenticationBearer API key
FormatJSON

Introduction

The SendAPI.email API sends transactional email. It takes and returns JSON, so send bodies with Content-Type: application/json. Every path starts with /v1.

Before you send

  1. Add your domain in the panel and add the DNS records it shows.
  2. Wait until the domain shows Verified.
  3. Create an API key on the API keys page.
Base URL
https://sendapi.email

Authentication

Send your API key in the Authorization header on every request. Keys start with ae_live_.

The full key is shown once, when you create it; we keep only its first characters and a hash. A missing, wrong or revoked key gets 401 invalid_api_key.

Keep keys on your server. Never put one in a browser or mobile app.
Header
Authorization: Bearer ae_live_...

Errors

Errors use HTTP status codes and return a JSON body with a code you can match on and a message you can show or log.

StatusCodeWhen
400invalid_requestA field, header or query parameter is missing or wrong. The message names it.
401invalid_api_keyThe API key is missing, wrong or revoked.
402out_of_creditsThe account has fewer credits than the email has recipients. Buy more on the Billing page.
403domain_not_foundThe domain of from is not in your account.
403domain_not_verifiedThe domain is in your account but isn't verified yet.
404not_foundNo email with this id, the address is not on the list, or no such path.
409idempotency_conflictThis Idempotency-Key was already used with a different request.
409cannot_removeThe address is on the list only for spam complaints or unsubscribes, which can't be removed.
413payload_too_largeThe request body is over 10 MB.
500internal_errorSomething failed on our side. Try again.
502provider_errorOur email provider did not take the address off its own list, so it stays on ours. Try again.
Error body400JSON
{
  "error": {
    "code": "invalid_request",
    "message": "subject is required"
  }
}

Idempotency

Send an Idempotency-Key header with POST /v1/emails to make retries safe. Use a value that is unique to the email, such as an order id, of 1 to 255 characters.

Keys are scoped to the API key. Repeating a request with the same key and the same body returns the same email's id and its current status, and sends nothing new. The same key with a different body gets 409 idempotency_conflict.

Header
Idempotency-Key: order-1042-receipt

Send an email

POST/v1/emails

Queues an email and returns 202 with its id. Sending happens right after, in the background; follow it with Get an email.

Body parameters

fromstringRequired
[email protected] or Your App <[email protected]>. The domain after @ must be one you added in the panel that shows Verified.
tostring or arrayRequired
1 to 50 addresses.
subjectstringRequired
One line, at most 998 characters.
htmlstring
The HTML body. Send html, text or both.
textstring
The plain-text body.
reply_tostring or array
Up to 10 addresses.
attachmentsarray
Up to 10 files. Each has filename (at most 255 characters), content (base64) and, optionally, content_type such as application/pdf.
headersobject
Up to 10 custom headers, name to value, such as {"List-Unsubscribe": "<https://yourapp.com/u/123>"}. Printable ASCII on one line; the value is 1 to 995 characters. From, To, Cc, Bcc, Subject, Reply-To, Date, Message-ID, MIME-Version, Content-Type, Content-Disposition, Return-Path and X-SES- headers can't be set.
trackboolean
Track opens and clicks. Default true. Tracking works in HTML emails only.
  • Each recipient uses one credit when the email is accepted. Credits for recipients that are never sent to, because they're suppressed or the email failed, come back to the account.
  • Unknown fields are refused, so a typo can't be ignored.
  • The request can be at most 10 MB, attachments included.
  • Recipients on your suppression list are left out when the email is sent. If none is left, the status becomes suppressed.
  • A temporary sending error is retried, 5 attempts in all, waiting 30 seconds, 2 minutes, 10 minutes, 30 minutes.
  • The body, attachments and header values are deleted once the email is sent, suppressed or failed. Their names stay.
RequestcURL
curl -X POST https://sendapi.email/v1/emails \
  -H "Authorization: Bearer $SENDAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-receipt" \
  -d '{
    "from": "Your App <[email protected]>",
    "to": [
      "[email protected]"
    ],
    "subject": "Your receipt",
    "html": "<p>Thanks for your order.</p>",
    "text": "Thanks for your order."
  }'
Response202JSON
{
  "id": "em_01JCB3Z4K7R2M9Q8T5V6W3X1",
  "status": "queued"
}

Get an email

GET/v1/emails/{id}

Returns an email's status, counts and every event so far, oldest first. Only emails of your account are found; any other id gets 404 not_found.

Response fields

statusstring
See Statuses and events.
suppressed_toarray
Recipients left out because they are on the suppression list.
attachments, headersarray
The names only; the content is deleted after sending.
attemptsnumber
How many times sending was tried.
last_errorstring or null
The last sending error, if any.
opens, clicksnumber
Counted opens and clicks. Events flagged as likely bots, and a click on the same link within 60 seconds of a counted one, are in events but not counted.
eventsarray
Each has type, at and detail, the event's details as the email provider or the API recorded them.
RequestcURL
curl https://sendapi.email/v1/emails/em_01JCB3Z4K7R2M9Q8T5V6W3X1 \
  -H "Authorization: Bearer $SENDAPI_API_KEY"
Response, detail shortened200JSON
{
  "id": "em_01JCB3Z4K7R2M9Q8T5V6W3X1",
  "status": "delivered",
  "from": "Your App <[email protected]>",
  "to": [
    "[email protected]"
  ],
  "suppressed_to": [],
  "reply_to": [],
  "subject": "Your receipt",
  "track": true,
  "attachments": [],
  "headers": [],
  "attempts": 1,
  "last_error": null,
  "opens": 0,
  "clicks": 0,
  "created_at": "2026-11-09T10:00:00.120Z",
  "sent_at": "2026-11-09T10:00:01.050Z",
  "delivered_at": "2026-11-09T10:00:02.000Z",
  "last_event_at": "2026-11-09T10:00:02.000Z",
  "events": [
    {
      "type": "queued",
      "at": "2026-11-09T10:00:00.120Z",
      "detail": {
        "api_key": "Production"
      }
    },
    {
      "type": "sent",
      "at": "2026-11-09T10:00:01.050Z",
      "detail": {
        "provider_message_id": "0110019a2b3c4d5e-example-000000"
      }
    },
    {
      "type": "delivery",
      "at": "2026-11-09T10:00:02.000Z",
      "detail": {
        "timestamp": "2026-11-09T10:00:02.000Z",
        "smtpResponse": "250 2.6.0 Message received"
      }
    }
  ]
}

Statuses and events

Delivery events only move a status forward, so one that arrives late never takes an email back, for example from delivered to sent.

Statuses

StatusMeaning
queuedAccepted and waiting to be sent.
sendingBeing handed off for delivery.
sentAccepted for delivery.
delayedDelivery is delayed by a temporary problem.
deliveredThe receiving server accepted it.
bouncedThe receiving server refused it.
complainedThe recipient marked it as spam.
rejectedRefused before delivery.
failedNot sent: refused for good, 5 attempts failed, or the service stopped mid-send. last_error says which.
suppressedEvery recipient is on your suppression list, so nothing was sent.

Event types

TypeMeaning
queuedThe API accepted the email.
sentAccepted for delivery; detail has provider_message_id.
retryA temporary sending error; it will be tried again.
failedSending stopped.
suppressedRecipients left out because they are on the suppression list.
deliveryDelivered to the receiving server.
delivery_delayDelivery is delayed.
bounceBounced.
complaintMarked as spam.
rejectRefused before delivery.
openOpened (HTML emails with tracking on).
clickA link was clicked (HTML emails with tracking on); detail has the link.
rendering_failureThe email could not be rendered.
subscriptionA subscription event from the email provider.

List suppression changes

GET/v1/suppressions

An address that hard-bounces is not emailed again from any of your domains; one that marks an email as spam is not emailed again from the domain that sent it. You can also add addresses in the panel. This endpoint lists the changes to that list, oldest first, so your app can keep its own copy.

Start without since, then pass the next_since of the last response. An entry appears again whenever it changes, for example when it is removed.

Query parameters

sincestring
The next_since of the previous response.
reasonstring
Only this reason: "bounce", "complaint", "unsubscribe", "manual".
domainstring
Only entries for this domain, plus those for every domain.
limitnumber
1 to 1000. Default 100.

Each entry

addressstring
The blocked address.
domainstring or null
The sending domain it is blocked for; null means every domain.
reasonstring
"bounce", "complaint", "unsubscribe", "manual"
email_idstring or null
The email that caused it, if any.
removed_at, removed_bystring or null
Set once the entry is removed; it no longer blocks.
When has_more is true, ask again with the new next_since right away.
RequestcURL
curl "https://sendapi.email/v1/suppressions?limit=100" \
  -H "Authorization: Bearer $SENDAPI_API_KEY"
Response200JSON
{
  "data": [
    {
      "address": "[email protected]",
      "domain": null,
      "reason": "bounce",
      "email_id": "em_01JCB3Z4K7R2M9Q8T5V6W3X1",
      "detail": {
        "bounce_type": "Permanent",
        "bounce_subtype": "General",
        "diagnostic_code": "smtp; 550 5.1.1 user unknown"
      },
      "created_by": null,
      "created_at": "2026-11-09T10:00:03.000Z",
      "removed_at": null,
      "removed_by": null
    }
  ],
  "next_since": "42",
  "has_more": false
}

Remove a suppression

DELETE/v1/suppressions/{address}

Lets an address receive email again. Only "bounce", "manual" entries can be removed. Spam complaints and unsubscribes stay and are listed in kept; if the address has only those, you get 409 cannot_remove.

URL-encode the address. A bounce for every domain is also taken off our email provider's own list; if the provider refuses, the address stays on your list and you get 502 provider_error.

RequestcURL
curl -X DELETE https://sendapi.email/v1/suppressions/[email protected] \
  -H "Authorization: Bearer $SENDAPI_API_KEY"
Response200JSON
{
  "address": "[email protected]",
  "removed": [
    {
      "address": "[email protected]",
      "domain": null,
      "reason": "bounce",
      "email_id": "em_01JCB3Z4K7R2M9Q8T5V6W3X1",
      "detail": {
        "bounce_type": "Permanent",
        "bounce_subtype": "General",
        "diagnostic_code": "smtp; 550 5.1.1 user unknown"
      },
      "created_by": null,
      "created_at": "2026-11-09T10:00:03.000Z",
      "removed_at": "2026-11-09T12:00:00.000Z",
      "removed_by": "API key Production"
    }
  ],
  "kept": []
}