Emails

Send transactional emails and retrieve their delivery status.

SMTP Bridge

Legacy systems can submit mail over SMTP and still use the same verified domains, rate limits, suppression checks, queueing, logs, and webhooks as the HTTP API.

text
Host: smtp.smtpfa.st
Port: 587 or 2525
Security: STARTTLS
Username: smtpfast
Password: YOUR_SMTPFAST_API_KEY

The API key must include the email:send scope. Port 2525 is available for environments that block standard SMTP submission ports.

POST/v1/emails

Send a single email. The request accepts all standard fields plus Resend-compatible fields like tags and headers.

The from address must belong to a verified domain.

Parameters

ParameterTypeRequiredDescription
fromstringYesSender email address (must be on a verified domain)
tostring | string[]YesRecipient email address or array of addresses
subjectstringYesEmail subject line
htmlstringNoHTML body of the email
textstringNoPlain text fallback body
ccstring | string[]NoCC recipients
bccstring | string[]NoBCC recipients
reply_tostring | string[]NoReply-to address(es)
tagsarrayNoArray of { name, value } pairs for categorization
headersobjectNoCustom email headers as key-value pairs

Request Body

json
{
  "from": "[email protected]",
  "to": ["[email protected]"],
  "cc": ["[email protected]"],
  "reply_to": "[email protected]",
  "subject": "Welcome!",
  "html": "<h1>Hello!</h1><p>Welcome to our platform.</p>",
  "text": "Hello! Welcome to our platform.",
  "tags": [
    { "name": "category", "value": "onboarding" }
  ],
  "headers": {
    "X-Entity-Ref-ID": "abc-123"
  }
}

Response

json
{
  "id": "email_abc123"
}
POST/v1/emails/batch

Send up to 100 emails in a single request. The body is a JSON array of email objects, each accepting the same fields as the single-send endpoint (from, to, subject, html/text, cc, bcc, reply_to, tags, headers, scheduled_at). Each email is queued and dispatched independently.

Validation runs on every item before any are queued, so a malformed item rejects the entire batch with a 400. Recipients on your suppression list are dropped per row; if every recipient on a single row is suppressed, that row is recorded with status "failed" while the rest of the batch still sends. The whole batch counts against your monthly quota up-front.

Request Body

json
[
  {
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Welcome!",
    "html": "<h1>Hello User 1!</h1>",
    "tags": [{ "name": "campaign", "value": "welcome" }]
  },
  {
    "from": "[email protected]",
    "to": ["[email protected]", "[email protected]"],
    "subject": "Weekly digest",
    "html": "<h1>Your week</h1>",
    "scheduled_at": "2026-04-10T14:00:00Z"
  }
]

Response

json
{
  "batch_id": "batch_abc123",
  "count": 2,
  "emails": [
    { "id": "email_abc123", "status": "queued" },
    { "id": "email_def456", "status": "scheduled" }
  ]
}
GET/v1/emails

List sent emails, newest first. Bodies are not included; fetch one by id for those. Requires the email:read scope.

Parameters

ParameterTypeRequiredDescription
limitintegerNoPage size, 1-100. Defaults to 50.
pageintegerNo1-based page number. Ignored when a cursor is given.
afterstringNoReturn emails older than this id. Not included in the result. Use either after or before, not both.
beforestringNoReturn emails newer than this id.
statusstringNoFilter by current status, e.g. delivered, bounced, scheduled.
tostringNoExact recipient address.

Response

json
{
  "data": [
    {
      "id": "email_abc123",
      "from": "[email protected]",
      "to": ["[email protected]"],
      "subject": "Welcome!",
      "status": "delivered",
      "source": "api",
      "created_at": "2026-04-05T12:00:00Z",
      "delivered_at": "2026-04-05T12:00:03Z",
      "bounced_at": null,
      "error_message": null
    }
  ],
  "page": 1,
  "limit": 50,
  "total": 1284,
  "has_more": true
}

To find what has not gone out yet, filter on status=scheduled. For per-event detail rather than per-email, use the event log, which needs the logs:read scope.

PATCH/v1/emails/:id

Reschedule an email that has not been sent yet. Only the send time can change; to change the content, cancel it and send again. Requires the email:send scope.

Parameters

ParameterTypeRequiredDescription
idstringYesThe email ID
scheduled_atstringYesNew send time in ISO 8601. Must be in the future and within 30 days.

Response

json
{
  "object": "email",
  "id": "email_abc123",
  "scheduled_at": "2026-04-09T09:00:00Z"
}
POST/v1/emails/:id/cancel

Stop an email that has not been sent yet. Works while it is scheduled or queued. Requires the email:send scope.

Parameters

ParameterTypeRequiredDescription
idstringYesThe email ID

Response

json
{
  "object": "email",
  "id": "email_abc123"
}

When cancelling is too late

Both of these return 409 once the send has started, and say which states they would have accepted. A message already handed to the provider cannot be recalled. Emails that belong to a broadcast are refused as well: cancel or reschedule the broadcast instead, so its recipient bookkeeping stays correct.

GET/v1/emails/:id

Retrieve the details and delivery events for a specific email.

Parameters

ParameterTypeRequiredDescription
idstringYesThe email ID returned from the send endpoint

Response

json
{
  "id": "email_abc123",
  "from": "[email protected]",
  "to": ["[email protected]"],
  "subject": "Welcome!",
  "html": "<h1>Hello!</h1>",
  "text": null,
  "status": "delivered",
  "source": "api",
  "created_at": "2026-04-05T12:00:00Z",
  "sent_at": "2026-04-05T12:00:01Z",
  "delivered_at": "2026-04-05T12:00:03Z",
  "bounced_at": null,
  "unsubscribed_at": "2026-04-06T08:14:22Z",
  "last_event": "unsubscribed",
  "events": [
    { "type": "queued", "timestamp": "2026-04-05T12:00:00Z" },
    { "type": "sent", "timestamp": "2026-04-05T12:00:01Z" },
    { "type": "delivered", "timestamp": "2026-04-05T12:00:03Z" },
    { "type": "unsubscribed", "timestamp": "2026-04-06T08:14:22Z" }
  ]
}

About unsubscribed

When the recipient hits the one-click List-Unsubscribe link tied to a delivered message, SMTPfast records an unsubscribed event on the email and sets unsubscribed_at. The top-level status field intentionally stays at its terminal delivery value (delivered, bounced, …) — unsubscribe is a post-delivery recipient action, not a delivery outcome. Check unsubscribed_at (or filter the events array) to detect opt-outs without losing the original delivery state.

Error Codes

CodeMeaning
400Invalid request body (missing required fields, malformed email)
401Missing or invalid API key
403Domain not verified or sending not allowed for this key
404Email ID not found
429Rate limit exceeded