Emails

Send transactional email and list recent sends. Migrating from Resend? See docs/migration.md.


Send email

POST /emails

Send a transactional email from a verified domain.

Base URL: https://api.supersendtx.com


Authentication

Authorization: Bearer stx_…

API keys are created in the dashboard (API Keys). Keys start with stx_.


Request body

Field Type Required Description
from string or { email, name? } Yes Must be on a verified domain
to string or string[] Yes Recipient(s)
subject string Yes Subject line
html string No* HTML body (htmlBody / html_body aliases accepted)
text string No* Plain-text body (textBody, text_body, and plain_body aliases accepted)
reply_to string or string[] No Reply-to address(es) (replyTo alias accepted)
cc string or string[] No CC recipient(s)
bcc string or string[] No BCC recipient(s)
tags { name, value }[] or object No Up to 10 key/value tags stored with the email
headers object No Custom headers; dangerous transport headers are rejected
attachments array No Up to 10 files, 10MB decoded each; executable/script extensions are blocked. Attachment aliases filename, contentType, content, and contentId are also accepted.
scheduled_at string No ISO 8601 timestamp. Creates a scheduled email instead of sending immediately (scheduledAt alias accepted)

* At least one of html or text is required.

Example

{
  "from": "you@yourdomain.com",
  "to": "user@example.com",
  "subject": "Hello from SuperSend TX",
  "html": "<p>It works.</p>"
}

With display name:

{
  "from": { "email": "you@yourdomain.com", "name": "Your App" },
  "to": ["user@example.com", "other@example.com"],
  "subject": "Hello",
  "html": "<p>Hi</p>",
  "text": "Hi",
  "reply_to": ["support@yourdomain.com"],
  "tags": [{ "name": "order_id", "value": "ord_123" }],
  "headers": { "X-Entity-ID": "ord_123" }
}

Sandbox self-test

Before your own domain is verified, you can self-test with the shared sandbox domain:

{
  "from": "noreply@mail.supersendtx.com",
  "to": "owner@example.com",
  "subject": "Sandbox check",
  "html": "<p>Sandbox works.</p>"
}

The sandbox sender is limited to the account email attached to the API key owner.


Responses

200 — Accepted

{
  "id": "msg_abc123…",
  "status": "sent"
}

Scheduled sends return:

{
  "id": "msg_abc123…",
  "status": "scheduled"
}

id is a stable public message ID (msg_…). Use it to correlate webhook events and dashboard activity.


Idempotency

Optional header on POST /emails:

Idempotency-Key: order-123

Keys are scoped per account, retained for 24 hours. Replays return the original response. Reusing a key with a different body returns 409 idempotency_conflict.

await client.emails.send({ …, idempotencyKey: 'order-123' })

Retrieve email

GET /emails/{id}

Returns one send by public id (msg_…).

const { email } = await client.emails.get('msg_abc123…')

Resend

POST /emails/{id}/resend

Creates a new send from a previously stored email (same from/to/subject/body, tags, and headers). Returns a new msg_… id. Attachment bytes are not stored, so attachments are omitted.

Dashboard: Emails → Resend on any row.

SDK:

await client.emails.resend('msg_abc123…')

CLI:

supersendtx emails resend --id msg_abc123…

Schedule, reschedule, and cancel

Add scheduled_at to POST /emails to store the message and enqueue it for later delivery:

{
  "from": "you@yourdomain.com",
  "to": "user@example.com",
  "subject": "Tomorrow",
  "html": "<p>See you tomorrow.</p>",
  "scheduled_at": "2026-08-01T12:00:00.000Z"
}

Scheduled emails can be updated only while their status is scheduled:

PATCH /emails/msg_abc123…
{ "scheduled_at": "2026-08-01T13:00:00.000Z" }

Cancel a scheduled email:

{ "cancel": true }

SDK:

await client.emails.update('msg_abc123…', { scheduledAt: '2026-08-01T13:00:00.000Z' })
await client.emails.cancel('msg_abc123…')

CLI:

supersendtx emails cancel --id msg_abc123…

Scheduled sends store message bodies so the worker can send them later. Attachments are not supported on scheduled emails in v1 because raw attachment data is not stored.


Batch send

POST /emails/batch

Send up to 100 emails in one request:

{
  "emails": [
    {
      "from": "you@yourdomain.com",
      "to": "user@example.com",
      "subject": "Hello",
      "html": "<p>Hi</p>"
    }
  ]
}

The response is index-aligned:

{
  "data": [
    { "index": 0, "id": "msg_abc123…", "status": "sent" }
  ]
}

Per-email validation or delivery failures are returned in that item’s error. Attachments are rejected for batch sends in v1.

SDK:

const result = await client.emails.batch([{ from, to, subject, html }])

List emails

GET /emails?limit=25&cursor=…

Returns recent sends for the authenticated API key’s account. limit is 1–100 (default 25). Use opaque cursor / next_cursor for pagination.

Responses include rate-limit headers: ratelimit-limit, ratelimit-remaining, ratelimit-reset.

200

{
  "emails": [
    {
      "id": "msg_abc123…",
      "from": "you@yourdomain.com",
      "to": ["user@example.com"],
      "cc": [],
      "bcc": [],
      "reply_to": [],
      "subject": "Hello",
      "status": "delivered",
      "last_event": "delivered",
      "bounce_reason": null,
      "tags": [],
      "scheduled_at": null,
      "cancelled_at": null,
      "created_at": "2026-07-25T12:00:00.000Z",
      "sent_at": "2026-07-25T12:00:01.000Z",
      "delivered_at": "2026-07-25T12:00:05.000Z",
      "bounced_at": null
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Status values: queued, scheduled, sent, delivered, bounced, failed, cancelled.

SDK

const { emails, next_cursor } = await client.emails.list({ limit: 10 })

Error format

All errors return:

{
  "error": {
    "message": "Human-readable message",
    "code": "validation_error",
    "details": {}
  }
}
Status When
400 Invalid JSON, missing fields, invalid from
401 Missing/invalid Bearer token or API key
403 from domain not verified for this key, or sandbox sender used for recipients outside the account email
409 Idempotency key conflict
429 Free plan send limit (plan_limit)
502 Upstream mail delivery error
503 Mail delivery temporarily unavailable

Attachment limits

  • Maximum 10 attachments per non-batch, non-scheduled send
  • Maximum 10MB decoded size per attachment
  • Blocked filename extensions: .exe, .bat, .cmd, .com, .scr, .js, .vbs, .dll

Examples

curl

curl -X POST https://api.supersendtx.com/emails \
  -H "Authorization: Bearer $SUPERSENDTX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from":"you@yourdomain.com","to":"user@example.com","subject":"Hello","html":"<p>Hi</p>"}'

Node.js

import { SuperSendTX } from 'supersendtx'

const client = new SuperSendTX(process.env.SUPERSENDTX_API_KEY!)
await client.emails.send({
  from: 'you@yourdomain.com',
  to: 'user@example.com',
  subject: 'Hello',
  html: '<p>Hi</p>',
})

Throws SuperSendTXError on failure (error.status, error.message, error.details).

CLI

supersendtx emails send \
  --from you@yourdomain.com \
  --to user@example.com \
  --subject "Hello" \
  --html "<p>Hi</p>" \
  --tag order_id=ord_123 \
  --schedule 2026-08-01T12:00:00.000Z

Requires SUPERSENDTX_API_KEY or --api-key.


OpenAPI

Machine-readable spec: openapi/supersendtx.yaml