Start typing to search the documentation.

to navigate·to open

API

Email API

Send transactional and templated email over HTTP — inline or templated bodies, batch and scheduled sends, delivery reports, senders, templates, balance, and error codes.

The Email API sends transactional and templated email from any client over HTTP, authenticated with a WRNexus API key.

Base URL & authentication

https://app.wrnexus.com

Email endpoints live under /email/* (and are aliased under /api/email/*). Authenticate every request with your API key:

Authorization: Bearer wrn_live_<your_api_key>
X-API-Key: wrn_live_<your_api_key>

Scopes: email:send is required to send; email:read is required for reports and discovery. A key with no explicit scopes is unrestricted.

Verified senders only. You can only send from an address whose domain has been verified for your workspace (configured under Email → Domains in the dashboard). An unverified from is rejected with SENDER_NOT_VERIFIED / DOMAIN_NOT_VERIFIED.

Send email

POST /email/send

Field Type Notes
from string Verified sender address. Defaults to the workspace’s main sender.
fromName string Optional display name shown to recipients.
to string | string[] Required. A single address or an array for a fan-out send.
subject string Required unless a template supplies one.
html string HTML body. Required unless text or template is given.
text string Plain-text body / fallback.
template string Stored template id or slug to render.
variables object Merge tags for the template, e.g. { "code": "4821" }.
scheduledAt string RFC3339 timestamp to queue a future send.

Inline subject + body:

curl https://app.wrnexus.com/email/send \
  -H "Authorization: Bearer wrn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "noreply@acme.com",
    "fromName": "Acme",
    "to": "customer@example.com",
    "subject": "Your OTP code",
    "html": "<p>Your code is <strong>4821</strong>. It expires in 10 minutes.</p>",
    "text": "Your code is 4821. It expires in 10 minutes."
  }'

Response 200 OK:

{
  "ok": true,
  "id": "snd_01HVZ...",
  "status": "sent",
  "from": "noreply@acme.com",
  "recipientCount": 1,
  "sentCount": 1,
  "failedCount": 0,
  "skippedCount": 0,
  "scheduledAt": null,
  "messageId": "f58a2939-2c4b-4f0a-9b7e-1f2a3b4c5d6e",
  "results": [
    { "to": "customer@example.com", "ok": true, "messageId": "f58a2939-...", "error": null, "skipped": false }
  ]
}

status is sent for an immediate send, scheduled when scheduledAt is in the future, or skipped when every recipient was suppressed/unsubscribed.

Template + variables:

curl https://app.wrnexus.com/email/send \
  -H "Authorization: Bearer wrn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "noreply@acme.com",
    "to": "customer@example.com",
    "template": "otp",
    "variables": { "code": "4821" }
  }'

Batch + scheduleto may be an array, and scheduledAt queues the send:

curl https://app.wrnexus.com/email/send \
  -H "Authorization: Bearer wrn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "noreply@acme.com",
    "to": ["a@example.com", "b@example.com"],
    "subject": "Monthly statement",
    "html": "<p>Your statement is ready.</p>",
    "scheduledAt": "2026-06-20T09:30:00Z"
  }'

Delivery report

GET /email/status?id=<messageId>

The delivery + engagement report (delivered / opened / clicked / bounced / complained) of a message you sent (scope email:read). The alias messageId is also accepted; an unknown id returns 404 NOT_FOUND.

{
  "id": "f58a2939-2c4b-4f0a-9b7e-1f2a3b4c5d6e",
  "messageId": "f58a2939-2c4b-4f0a-9b7e-1f2a3b4c5d6e",
  "status": "delivered",
  "from": "noreply@acme.com",
  "to": "customer@example.com",
  "subject": "Your OTP code",
  "createdAt": "2026-06-18T14:30:00.000000",
  "events": [
    { "type": "delivered", "recipient": "customer@example.com", "url": null, "reason": null, "occurredAt": "2026-06-18T14:30:03.000000" },
    { "type": "opened", "recipient": "customer@example.com", "url": null, "reason": null, "occurredAt": "2026-06-18T14:32:10.000000" }
  ]
}

Discovery & balance

GET /email/senders

Your verified from-addresses and their sending domains (scope email:read). Use from (or senderId) as the send from value.

GET /email/templates

Your stored templates, including the slug/id to reference and the variables (merge tags) you must supply (scope email:read).

GET /email/balance

Remaining email credits for the key’s active workspace (scope email:read).

{
  "workspaceId": "ws_01HVZ...",
  "creditType": "email",
  "balance": 9820
}

Error responses

Errors use the same flat contract as the rest of the API:

{
  "code": "SENDER_NOT_VERIFIED",
  "message": "The from address is not a verified sender for this workspace."
}
Code HTTP Description
UNAUTHENTICATED 401 Missing, invalid, expired, or revoked API key.
FORBIDDEN 403 Key lacks email:send/email:read, or insufficient role.
IP_NOT_ALLOWED 403 Caller IP is not on the organization’s allowlist.
NO_RECIPIENTS 400 No to provided.
INVALID_RECIPIENT 400 A recipient address is malformed or too long.
SUBJECT_REQUIRED 400 No subject and no template to supply one.
BODY_REQUIRED 400 No html, text, or template body.
INVALID_TEMPLATE 400 The template id/slug was not found.
INVALID_SEND_TIME 400 scheduledAt is not an RFC3339 timestamp.
SENDER_NOT_VERIFIED 409 The from address is not a verified sender.
DOMAIN_NOT_VERIFIED 409 The sender’s domain is not verified (or expired).
INSUFFICIENT_CREDITS 402 The workspace’s email balance is below the recipient count.
NO_WORKSPACE 409 The organization has no active workspace to send from.
NOT_FOUND 404 Message (report lookup) not found.
EMAIL_SEND_FAILED 502 The mail backend rejected the send.
INTERNAL_ERROR 500 An unexpected server error.

See also

  • Email SDK — calling the Email API from your language.
  • Email Webhooksemail.delivered, email.opened, email.clicked, email.bounced, and more.
  • Authentication — create and secure an API key.