Skip to content

Test Mode

Test your integration without sending real emails. Test mode API keys simulate the full email pipeline -- validation, queueing, and events -- without delivering anything.

Test API Keys

Test mode is activated by using an API key that starts with sk_test_ instead of sk_live_. Create test keys from the dashboard under Configuration → API keys.

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

// Use a test key -- no real emails will be sent
const poststack = new PostStack('sk_test_...');

// This email is validated and queued, but never delivered
const { id } = await poststack.emails.send({
  from: 'you@yourdomain.com',
  to: ['user@example.com'],
  subject: 'Test Email',
  html: '<p>This is a test.</p>',
});

// id is still returned, and events are simulated

How Test Mode Works

Test mode behaves identically to live mode with these differences:

No actual delivery

Emails are validated and queued but never sent to the recipient's mail server. No SMTP connection is made.

Simulated events

PostStack automatically generates simulated delivery events (queued, sent, delivered) so you can test your webhook handlers and event processing.

Full validation

All request validation is applied -- domain verification checks, schema validation, permission checks, and rate limits all work normally.

No billing impact

Test emails are not counted toward your usage quota and do not incur any charges.

Test Mode with cURL

Simply use your test API key in the Authorization header:

bash
curl -X POST https://api.poststack.dev/emails \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "you@yourdomain.com",
    "to": ["user@example.com"],
    "subject": "Test Email",
    "html": "<p>This is a test.</p>"
  }'

Test Mode with Python and Go

Every SDK treats a test key the same way — there is no flag to set. Read the key from the environment so staging and CI get an sk_test_ key and production gets an sk_live_ key without a code change.

python
import os
from poststack import PostStack

ps = PostStack(api_key=os.environ["POSTSTACK_API_KEY"])  # sk_test_... in CI

result = ps.emails.send({
    "from": "you@yourdomain.com",
    "to": ["user@example.com"],
    "subject": "Test Email",
    "html": "<p>This is a test.</p>",
})
assert result.get("test_mode") is True
go
client := poststack.NewClient(os.Getenv("POSTSTACK_API_KEY")) // sk_test_... in CI

res, err := client.Emails.Send(ctx, &poststack.SendEmailInput{
    From:    "you@yourdomain.com",
    To:      []string{"user@example.com"},
    Subject: "Test Email",
    Html:    "<p>This is a test.</p>",
})

Testing Webhooks

Test mode emails still trigger webhook deliveries with simulated events. This makes it easy to develop and test your webhook handler end-to-end:

typescript
// 1. Create a webhook (works with both test and live keys)
await poststack.webhooks.create({
  url: 'https://yourdomain.com/webhooks/poststack',
  events: ['email.delivered', 'email.bounced'],
});

// 2. Send a test email
await poststack.emails.send({
  from: 'you@yourdomain.com',
  to: ['user@example.com'],
  subject: 'Test',
  html: '<p>Testing webhooks</p>',
});

// 3. Your webhook endpoint receives simulated events:
// - email.sent
// - email.delivered
// Both carry "test_mode": true in their data object, so a handler can
// ignore them in production.

Test Mode over SMTP

A test key works as an SMTP password exactly like a live one — authentication succeeds and the server answers 250 — but the message is simulated and never leaves PostStack. Because an SMTP client has no other channel to tell you, the acceptance line says so explicitly:

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

If you are wiring PostStack into a framework or platform that only exposes SMTP settings (Supabase, Rails, Django, WordPress), point it at a sk_live_ key as soon as you want real deliveries.

Identifying Test Emails

Every email records how it was sent. Send responses carry test_mode: true (absent on a live send), email objects returned by emails.get() and emails.list() carry testMode, and the dashboard shows a Test mode badge in place of the delivery status.

To keep environments separate from the start, use a dedicated sk_test_ key per environment and filter on tags or domain_id when listing emails.

Test sends are excluded from billing usage — they never consume a send from your plan's monthly allowance. They are not excluded from analytics: a test send is recorded as a normal delivered email, so it appears in the emails list and counts towards the dashboard's sent/delivered/bounced totals, the timeseries, and the provider and geography breakdowns. If your test volume is large enough to distort those numbers, tag every test send (or route it through a dedicated test domain) and filter accordingly.

typescript
// Tag every test send so you can filter it out later
await poststack.emails.send({
  from: 'you@yourdomain.com',
  to: ['user@example.com'],
  subject: 'Test Email',
  html: '<p>This is a test.</p>',
  tags: ['env:test'],
});

// Filter just your test sends:
const { data } = await poststack.emails.list({ tag: 'env:test' });

Going Live Checklist

  • Your sending domain is verified — test mode applies the same domain checks, so a send that passes with a test key will not fail on this point with a live key.
  • Production reads an sk_live_ key from its secret store; no test key is baked into a build or container image.
  • Your webhook handler ignores (or logs separately) events whose data carries test_mode: true.
  • Checks that assert test_mode run only in CI and staging, never against production.

Related