SDK
Email SDK
Official WRNexus Email SDK — install and configure from the environment, construct the shared client, and call email.send / status / senders / templates / balance with typed responses, AMP support, and a structured error hierarchy.
The Email product is exposed as the client.email namespace on the same
WrNexusClient used for SMS. It is a thin,
typed wrapper over the Email API: the namespace is lazily created
over the client’s single shared transport — base URL, Authorization header,
timeout and JSON codec — so it reuses the same configuration, auth and error
parsing as every other product. Send inline or templated bodies (including
AMP for Email), read delivery + engagement reports, and list
senders, templates and balance.
Same client, same key.
client.emailshares the client you already configure forclient.sms— the sameWRNEXUS_API_KEY, the same base URL, and the same flat{ code, message }error contract. Constructing the client once lets any module in your project send mail without threading config around.
1. Environment & prerequisites
Install the SDK for your language; the namespace ships in the same package as SMS:
npm install @wrnexus/sdk # JavaScript / TypeScript (Node 18+)
pip install wrnexus # Python 3.8+
Every SDK reads its configuration from the environment by default. Explicit constructor options always override the environment.
| Setting | Env var | Default |
|---|---|---|
| API key | WRNEXUS_API_KEY |
(required) |
| Base URL | WRNEXUS_API_BASE_URL |
https://app.wrnexus.com |
| Timeout | WRNEXUS_TIMEOUT_MS |
30000 |
export WRNEXUS_API_KEY="wrn_live_xxx"
Your key needs the email:send scope to send and email:read to read
reports, senders, templates and balance (a key with no explicit scopes is
unrestricted). You can only send from a verified sender whose domain is
verified under Email → Domains. Create and scope your key under
Account → API Configuration — see
Authentication.
2. Initialization
The client owns one configured transport; client.email is a namespace over it.
Construct the client once and reuse it.
import { WrNexusClient } from '@wrnexus/sdk';
// Reads WRNEXUS_API_KEY (and optional base URL / timeout) from the environment.
const client = new WrNexusClient();
// Or configure explicitly — explicit values win over the environment:
const explicit = new WrNexusClient({
apiKey: 'wrn_live_xxx',
baseUrl: 'https://app.wrnexus.com',
timeoutMs: 15_000,
});
client.email; // the Email namespace, lazily created over the shared transport
from wrnexus import WrNexusClient
client = WrNexusClient() # reads WRNEXUS_API_KEY
explicit = WrNexusClient(
api_key="wrn_live_xxx",
base_url="https://app.wrnexus.com",
timeout=15.0, # seconds
)
A missing API key fails fast at construction (WrNexusConfigError in Python,
WrNexusError in JS/TS) before any request is made.
3. Methods
client.email exposes the same surface in every language:
| Method | Required scope | Description |
|---|---|---|
send(...) |
email:send |
Send a transactional or templated email to one or many recipients (optionally scheduled / AMP). Returns id, status, per-recipient results, and counts. |
status(id) |
email:read |
Delivery + engagement report (delivered / opened / clicked / bounced / complained) for a message you sent, including its events. |
senders() |
email:read |
List the workspace’s verified from-addresses and their sending domains. |
templates() |
email:read |
List stored templates with their slug/id and required variables (merge tags). |
balance() |
email:read |
Remaining email credits for the key’s active workspace. |
client.me() returns the identity the key resolves to (key id, organization,
scopes) — handy to verify a key works.
4. Parameters
send(...) parameters — names are shown in their JS/TS form; Python uses
snake_case (from_ carries a trailing underscore because from is a Python
keyword):
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
to |
string | string[] |
Yes | — | A single recipient or an array for a fan-out send. |
from |
string |
No | workspace main sender | Verified sender address. Defaults to the workspace’s main sender. |
fromName |
string |
No | null |
Display name shown to recipients. |
subject |
string |
Cond.* | — | Subject line. Required unless a template supplies one. |
html |
string |
Cond.* | — | HTML body. Required unless text or template is given. |
text |
string |
No | null |
Plain-text body / fallback. |
template |
string |
Cond.* | — | Stored template id or slug to render. |
variables |
object (str→any) |
No | {} |
Merge tags for the template, e.g. { code: '4821' }. |
amp |
string |
No | null |
A validated AMP4EMAIL (text/x-amp-html) part shipped alongside html; invalid AMP is rejected with 422 INVALID_AMP. |
scheduledAt |
string (RFC 3339) |
No | null |
Future send time. Omit/null sends immediately. |
* Provide a body via at least one of html, text, or template, and a
subject either directly or via the template.
Other methods: status(id) takes the id (alias messageId) string
returned by send; senders(), templates(), balance() and me() take no
parameters.
5. Function-calling examples
JavaScript / TypeScript
import { WrNexusClient } from '@wrnexus/sdk';
const client = new WrNexusClient();
// Templated send to a single recipient:
const res = await client.email.send({
from: 'noreply@acme.com',
fromName: 'Acme',
to: 'customer@example.com',
template: 'otp',
variables: { code: '4821' },
});
console.log(res.id, res.status, res.sentCount);
const report = await client.email.status(res.id);
const senders = await client.email.senders();
const templates = await client.email.templates();
const balance = await client.email.balance();
const identity = await client.me();
Sample send response:
{
"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 }
]
}
Batch + schedule — to accepts an array and scheduledAt queues the send:
await client.email.send({
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',
});
Python
from wrnexus import WrNexusClient
with WrNexusClient() as client: # closes the HTTP pool on exit
res = client.email.send(
to="customer@example.com",
from_="noreply@acme.com",
subject="Your OTP code",
html="<p>Your code is <strong>4821</strong>.</p>",
)
print(res.id, res.status) # "snd_01HVZ..." "sent"
report = client.email.status(res.id)
senders = client.email.senders()
templates = client.email.templates()
balance = client.email.balance()
Sample status report:
{
"id": "f58a2939-...",
"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", "occurredAt": "2026-06-18T14:30:03.000000" },
{ "type": "opened", "recipient": "customer@example.com", "occurredAt": "2026-06-18T14:32:10.000000" }
]
}
6. Error handling
Every non-2xx response rejects/raises a typed error from the shared hierarchy;
the server’s flat { code, message } contract is parsed onto the error (when the
body isn’t JSON — e.g. a 502 HTML page — the SDK synthesizes BAD_GATEWAY /
INTERNAL_ERROR so callers always get a typed, retryable error).
import {
WrNexusAuthError, // 401 / 403 — fix the key, scope, or IP
WrNexusServerError, // 5xx — retryable; .isBadGateway for the 502 case
WrNexusApiError, // other 4xx — .statusCode, .code, .message
WrNexusTransportError, // no HTTP response (DNS, timeout) — retryable
} from '@wrnexus/sdk';
try {
await client.email.send({ to: 'customer@example.com', subject: 'Hi', text: 'Hi' });
} catch (err) {
if (err instanceof WrNexusApiError) {
console.error(err.statusCode, err.code, err.message);
}
}
from wrnexus import (
WrNexusAuthError, WrNexusServerError, WrNexusApiError, WrNexusTransportError,
)
try:
client.email.send(to="customer@example.com", subject="Hi", text="Hi")
except WrNexusApiError as err:
print(err.status_code, err.code, err.message)
Branch on the HTTP status and code:
| Code | HTTP | Error class | Retry? |
|---|---|---|---|
UNAUTHENTICATED |
401 | WrNexusAuthError |
No — fix the key. |
FORBIDDEN |
403 | WrNexusAuthError |
No — missing scope/role. |
IP_NOT_ALLOWED |
403 | WrNexusAuthError |
No — caller IP not allowlisted. |
NO_RECIPIENTS |
400 | WrNexusApiError |
No — provide to. |
INVALID_RECIPIENT |
400 | WrNexusApiError |
No — malformed address. |
SUBJECT_REQUIRED |
400 | WrNexusApiError |
No — add a subject/template. |
BODY_REQUIRED |
400 | WrNexusApiError |
No — add html/text/template. |
INVALID_TEMPLATE |
400 | WrNexusApiError |
No — unknown template id/slug. |
INVALID_AMP |
422 | WrNexusApiError |
No — fix the AMP4EMAIL document. |
INVALID_SEND_TIME |
400 | WrNexusApiError |
No — bad scheduledAt. |
SENDER_NOT_VERIFIED |
409 | WrNexusApiError |
No — verify the sender first. |
DOMAIN_NOT_VERIFIED |
409 | WrNexusApiError |
No — verify the domain. |
INSUFFICIENT_CREDITS |
402 | WrNexusApiError |
No — top up email credits. |
NO_WORKSPACE |
409 | WrNexusApiError |
No — no active workspace. |
NOT_FOUND |
404 | WrNexusApiError |
No — unknown message id. |
EMAIL_SEND_FAILED |
502 | WrNexusServerError |
Yes — transient; back off. |
INTERNAL_ERROR |
500 | WrNexusServerError |
Yes — transient; back off. |
| (none — no response) | — | WrNexusTransportError |
Yes — DNS/timeout; back off. |
Retry only 5xx (WrNexusServerError) and transport errors with exponential
backoff; 4xx are caller errors and must not be retried. See the
Email API error table for the full list.
See also
- Email API — the raw HTTP endpoints these methods call.
- SMS SDK — the same client, the
client.smsnamespace. - Authentication — create and secure an API key.
- Email Webhooks — receive delivery and engagement events.