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
fromis rejected withSENDER_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 + schedule — to 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 Webhooks —
email.delivered,email.opened,email.clicked,email.bounced, and more. - Authentication — create and secure an API key.