How do I set up automated (E2E) testing?
Build an end-to-end test: use a sending_access API key, send to the mailbox simulator addresses, then assert on the returned email id and the webhook events.
You can verify your whole sending integration automatically — request, response, and event delivery — without mailing real people. The recipe: send to the mailbox simulator addresses with a scoped key, assert on the returned email id, then assert on the webhook events MailBlastr fires for that email.
What to set up
- 1Use a sending_access key
Create a dedicated
sending_accessAPI key for tests (see Create API key). It can send but cannot delete domains or other keys, so a leaked CI secret is far less dangerous. Store it as a CI secret, never in the repo. - 2Send to simulator addresses
Send to
delivered@,bounced@, andcomplained@mailblastr.devto exercise the delivered, bounce, and complaint paths deterministically — from your verified domain. - 3Assert on the response
A successful
POST /emailsreturns{ "id": "…" }with HTTP 200. Capture thatid; everything downstream keys off it. - 4Assert on events
Confirm the expected event arrives — either by receiving a webhook for that
id, or by pollingGET /emails/:id, whose response includes aneventsarray.
Example test
Send to the delivered simulator, then poll the email until a delivered event shows up. The same pattern works for bounced@ (expect a bounced event) and complained@ (expect a complained event).
import { MailBlastr } from 'mailblastr';
const mb = new MailBlastr(process.env.MAILBLASTR_TEST_KEY); // a sending_access key, from CI secrets
async function send(to) {
const { data, error } = await mb.emails.send({
from: 'Acme <hello@yourdomain.com>',
to: [to],
subject: 'E2E delivery test',
html: '<p>ping</p>',
});
if (error) throw new Error(`send failed: ${JSON.stringify(error)}`);
return data.id; // assert: id is a non-empty string
}
async function waitForEvent(id, type, { tries = 20, delayMs = 3000 } = {}) {
for (let i = 0; i < tries; i++) {
const { data: email } = await mb.emails.get(id);
if ((email?.events || []).some((e) => e.type === type)) return email;
await new Promise((r) => setTimeout(r, delayMs));
}
throw new Error(`timed out waiting for ${type} on ${id}`);
}
// Delivered path
const id = await send('delivered@mailblastr.dev');
await waitForEvent(id, 'delivered');
console.log('delivered ✓');A CI flow
- Store the
sending_accesskey as an encrypted CI secret (e.g.MAILBLASTR_TEST_KEY). - On each run, send one email per path:
delivered@,bounced@,complained@mailblastr.dev. - Assert each
POST /emailsreturns200with a stringid. - Wait for the matching event —
delivered,bounced,complained— via your webhook receiver or by pollingGET /emails/:id. - Fail the build if any expected event does not arrive within the timeout.
id, and let your test wait on that. Polling GET /emails/:id is the simplest fallback when a receiver is hard to expose from CI.Browser E2E with Playwright
If you drive a real browser flow (e.g. a Next.js app with a route that sends), Playwright is a natural fit. Expose an endpoint that triggers a send to a simulator address, then exercise it from a test. There are two approaches, and you’ll typically want both:
- Call the real API — drives the full path including MailBlastr’s response, so it catches integration regressions. It does consume your daily quota, so keep these few.
- Mock the response — intercept the request with
page.route(...)and fulfill it with a canned{ "id": "…" }body. This tests *your* app’s flow without hitting the API or spending quota.
// e2e/app.spec.ts
import { test, expect } from '@playwright/test';
test('calls the real MailBlastr API', async ({ page }) => {
await page.goto('http://localhost:3000/api/send');
// The route returns the email id on success
await expect(page.getByText('id')).toBeVisible();
});A minimal playwright.config.ts mainly needs testDir (where your specs live), outputDir (where results go), a webServer block so Playwright boots your app before the run, and a projects array for the browsers to test:
import { defineConfig, devices } from '@playwright/test';
import path from 'path';
const baseURL = 'http://localhost:3000';
export default defineConfig({
timeout: 30 * 1000,
testDir: path.join(__dirname, 'e2e'),
outputDir: 'test-results/',
retries: 2,
webServer: {
command: 'npm run dev',
url: baseURL,
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI,
},
use: { baseURL, trace: 'retry-with-trace' },
projects: [
{ name: 'Desktop Chrome', use: { ...devices['Desktop Chrome'] } },
{ name: 'Desktop Firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'Mobile Safari', use: devices['iPhone 12'] },
],
});Then install the browsers and run the suite:
npx playwright install
npx playwright testdelivered@mailblastr.dev) rather than a real inbox, so the run never harms your deliverability.