Skip to content

Test mode

Send with an sk_test_ key and PostStack runs the real request checks, records the email as delivered and fires its webhooks — without handing anything to a mail server.

Get a test key#

  1. Create the key

    In the dashboard, open Configuration → API keys, choose Create API key and set Mode to Test — simulates delivery, nothing is sent. Over the API, pass "mode": "test" to POST /api-keys.

  2. Use it where you would use a live key

    Test keys start with sk_test_. There is no flag to set in the SDKs or the request: the key decides. Read it from the environment so CI and staging get a test key and production a live one without a code change.

import { PostStack } from '@poststack.dev/sdk';

// sk_test_… in CI and staging, sk_live_… in production
const poststack = new PostStack(process.env.POSTSTACK_API_KEY!);

const res = await poststack.emails.send({
  from: 'hello@acme.com',
  to: ['user@example.com'],
  subject: 'Test email',
  html: '<p>This is a test.</p>',
});

res.test_mode; // true with a test key, absent with a live key

What a test send checks#

A test send goes through the same request pipeline as a live one, up to the moment the message would be queued. A request that fails here with a test key fails the same way with a live key:

  • Authentication, permissions and rate limits.
  • Request validation — addresses, subject, body, attachment count and size.
  • The from domain is added and verified, and the key is allowed to send from it.
  • The template exists and is published; variables render.
  • Suppressed recipients are removed (an email.suppressed webhook fires), and a send to only suppressed recipients fails with 422.
  • The spam scan: content at the block threshold fails with 422 exactly as it would live.
  • Idempotency keys replay and conflict exactly as they do live.

What it does not do#

Live sendTest send
Queued, then handed to the mail serverStored straight away as delivered; no connection to any mail server
scheduled_at delays itIgnored — marked delivered immediately, so it cannot be cancelled or rescheduled
Counts toward your monthly email allowanceNever counts
New and Free accounts: counts toward the daily cap on sends to external addressesNever counts
Open pixel and click tracking added to the HTMLHTML stored as sent; no tracking
Real delivered, bounced, opened, clicked and complained eventsAlways queued → sent → delivered; nothing after
Response has spam_scoreResponse has test_mode: true and no spam_score

Bounces and complaints cannot be simulated

A test send always ends in delivered, and the dashboard's Send test event sends an event of type test. To exercise bounce or complaint handling, post a sample payload from Webhooks to your handler yourself, or replay a stored delivery of a real event.

Webhooks in test mode#

Every test send fires email.sent and then email.delivered to your webhook endpoints, in that order, so you can develop a handler end to end before sending real mail. Both payloads carry "test_mode": true in their data:

json
{
  "type": "email.delivered",
  "created_at": "2026-09-29T10:00:00.912Z",
  "data": {
    "email_id": "em_a2r19yq5nah5fx3ziepm3ehu",
    "from": "hello@acme.com",
    "to": ["user@example.com"],
    "subject": "Test email",
    "test_mode": true
  }
}

In production, ignore or log separately any event whose data has test_mode: true.

Test mode over SMTP#

A test key works as an SMTP password exactly like a live one: authentication succeeds and the server accepts the message with 250. Since an SMTP client has no other way to learn that nothing was delivered, the reply says so:

text
250 Accepted em_a2r19yq5nah5fx3ziepm3ehu in TEST MODE - simulated only, nothing was delivered. Use a live (sk_live_) API key to send real mail.

Frameworks and platforms that only take SMTP settings (Supabase, Rails, Django, WordPress) usually hide that line. Switch them to an sk_live_ key as soon as you want real deliveries.

Telling test emails apart#

  • Send responses carry test_mode: true (absent on a live send), and so does every accepted element of a batch response.
  • Email objects from GET /emails and GET /emails/:id have testMode: true.
  • The dashboard shows a Test mode badge in place of the delivery status.

Test sends are left out of billing, but not out of analytics: they appear in the email list and count toward the dashboard's sent and delivered totals and its provider and geography breakdowns. If your test volume would distort those numbers, tag test sends and filter on the tag — the list has no test-mode filter.

typescript
await poststack.emails.send({
  from: 'hello@acme.com',
  to: ['user@example.com'],
  subject: 'Test email',
  html: '<p>This is a test.</p>',
  tags: ['env-test'],
});

const { data } = await poststack.emails.list({ tag: 'env-test' });

Going live#

  • Production reads an sk_live_ key from its secret store; no test key is baked into a build or container image.
  • The sending domain is verified — test mode already enforced this, so a send that passed with a test key will not fail on it live.
  • New accounts are limited in how many sends to external addresses they can make per day while they warm up. A test key never meets that limit, so plan your first live volume accordingly.
  • Your webhook handler ignores, or logs separately, events with test_mode: true.
  • Assertions on test_mode run only in CI and staging.

Troubleshooting#

Everything returns 202 but no email arrives

Check the key prefix. An sk_test_ key never delivers; the response's test_mode: true and the dashboard's Test mode badge confirm it.

422 Domain is not verified with a test key

Test mode applies the same domain checks as live mode. Verify the domain under Domains first.

Cancelling a scheduled test email returns 422

Test sends are marked delivered immediately, even with scheduled_at, so there is nothing left to cancel or reschedule. Test scheduling logic with a live key.

Next steps

Was this page helpful?

Related