Sendara gives you two ways to test. The sandbox takes a test key. It simulates a delivered, a bounced and a complained outcome. It sends no real mail, it bills nothing, and it still calls your webhooks. A verified test recipient receives real mail at one of your own addresses, at no charge, so you can check the message on your verified domain before you launch.
Test keys
Every account holds a live key and a test key. A test key (sk_test_…) runs the full pipeline: idempotency, suppression, templates, events, and webhooks. It does not give the message to a real provider. Sendara sends nothing and charges nothing. A live key (sk_live_…) sends real mail, and Sendara charges for it.
To move from the sandbox to production, change a single environment variable from sk_test_… to sk_live_…. The request shape is identical.
Simulated outcomes
In the sandbox, the recipient address drives the simulated outcome. Sendara reads the local-part (the part before the @), so the domain can be anything. Send to bounced@ to drive a bounce, complained@ to drive a complaint, or anything else to get a clean delivery.
| Recipient local-part | Final status | Events that fire |
|---|---|---|
delivered@ | delivered | sent → delivered |
bounced@ / bounce@ | bounced | sent → bounced |
complained@ / complaint@ | complained | sent → complained |
anything else | delivered | sent → delivered |
The example below drives a simulated bounce. Swap the local-part to steer the outcome. The rest of the request stays the same.
import { Sendara } from "sendara";
const sendara = new Sendara("sk_test_xxx");
await sendara.emails.send({
from: "[email protected]",
to: "[email protected]",
subject: "Sandbox bounce",
html: "<p>This drives a simulated bounce.</p>",
});sent before its terminal event. A simulated bounced or complainedoutcome does not add the address to your suppression list. This keeps the sandbox addresses reusable. A real permanent SES bounce or complaint does suppress the recipient in production.Webhooks in the sandbox
Sandbox sends use the same signed webhook envelope and event types as production, so you can test your handler end to end. The nested payload carries a synthetic detail instead of an Amazon SES event. Sendara delivers each event as a POST with these headers:
Sendara-Event-Id: unique per event. Sendara reuses it across the retries of the same event, so use it to dedupe.Sendara-Event-Type:sent, followed by the simulated terminal event (delivered,bounced, orcomplained).Sendara-Timestamp: the Unix epoch seconds at the time Sendara computed the signature.Sendara-Signature: the HMAC signature (see below).
{
"event_id": "evt_2",
"event_type": "delivered",
"message_id": "msg_a1b2c3",
"account_id": "acc_7Hb3Kp",
"payload": { "detail": "simulated delivery (sandbox)" },
"occurred_at": "2026-06-14T10:00:12Z",
"created_at": "2026-06-14T10:00:12Z"
}Verify the signature
Verify every webhook before trusting it. The signature is HMAC-SHA256(secret, "{timestamp}.{raw_body}"), hex-encoded. Build the signed string by concatenating the Sendara-Timestamp header, a literal ., and the exact raw request body. Then compare in constant time.
import {
verifyWebhook,
WebhookVerificationError,
} from "sendara";
export function verifySendaraWebhook(req: {
rawBody: string;
headers: Record<string, unknown>;
}): boolean {
try {
verifyWebhook(
req.rawBody,
req.headers,
process.env.SENDARA_WEBHOOK_SECRET!,
{ toleranceSeconds: 5 * 60 },
);
return true;
} catch (error) {
if (error instanceof WebhookVerificationError) return false;
throw error;
}
}Treat handlers as idempotent. Sendara can deliver an event more than once, and a retry reuses the same Sendara-Event-Id. Sendara retries a failed delivery with a backoff. Respond 2xx to acknowledge.
Verified test recipients
The sandbox simulates an outcome, and it puts no real mail in a real inbox. Register a verified test recipient to send real mail from your verified domain to an address that you own. Open the message there to check how it renders in that mail client, and to confirm that your DKIM signature and your sender name are correct. The send costs nothing, and it never touches your live list.
The guardrails:
- Up to 3 test recipients per account.
- Each verified address accepts at most 10 test sends per day (per UTC day).
- A send uses a live key with
test_send: true, and Sendara never bills it.
Register & verify
Register an address with POST /v1/test-recipients (requires the admin scope). Sendara emails that address a verification link. Once the recipient clicks it, the address flips to verified and is eligible for test sends.
curl https://api.sendara.dev/v1/test-recipients \
-H "Authorization: Bearer sk_live_admin_xxx" \
-H "Content-Type: application/json" \
-d '{ "email": "[email protected]" }'{
"id": "tr_4f1a8c2d9e",
"email": "[email protected]",
"status": "pending",
"created_at": "2026-06-14T10:00:00Z"
}List your recipients with GET /v1/test-recipients (requires read) to check verification status. You can re-send the verification email with POST /v1/test-recipients/{id}/resend, or remove an address with DELETE /v1/test-recipients/{id} (both require admin).
{
"recipients": [
{ "id": "tr_4f1a8c2d9e", "email": "[email protected]",
"status": "verified", "verified_at": "2026-06-14T10:02:30Z",
"created_at": "2026-06-14T10:00:00Z" }
]
}Send a real test email
After the address reaches verified, send to it with a normal POST /v1/send using a live key and test_send: true. The destination must be a verified test recipient on your account, and the per-day cap applies. Sendara skips the spend cap, because the send is free.
await sendara.emails.send({
from: "[email protected]",
to: "[email protected]",
subject: "Production smoke test",
html: "<p>Real email, no charge.</p>",
testSend: true,
});Errors
Two error codes are specific to this flow:
recipient_not_verified(403): the destination is not a verified test recipient on your account. Register and verify it first, or droptest_sendto send normally.test_send_daily_limit(429): this address used its 10 free test sends for the current UTC day. Wait for the next day or use a different verified address.
A request to register a fourth address returns too_many_test_recipients (422). Delete one first.
From sandbox to production
A reliable path to your first real production send:
- Build against
sk_test_…and drivedelivered@,bounced@, andcomplained@to exercise every branch and your webhook handler. - Verify your sending domain (see Domains & deliverability).
- Register a verified test recipient. Send a real message with
test_send: trueto check how your verified domain renders the message. The send costs nothing. - Swap to
sk_live_…and ship.