Start typing to search the documentation.

to navigate·to open

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.email shares the client you already configure for client.sms — the same WRNEXUS_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.sms namespace.
  • Authentication — create and secure an API key.
  • Email Webhooks — receive delivery and engagement events.