@mailqa/client
v0.1.0
Published
Official MailQA client — capture and assert on test email from automated suites.
Maintainers
Readme
@mailqa/client
Capture and assert on test email from automated suites — no sleep(), no mail-log scraping.
npm install --save-dev @mailqa/clientQuick start
import { MailQA } from '@mailqa/client';
const mailqa = new MailQA({ apiKey: process.env.MAILQA_API_KEY! });
// One throwaway inbox per test run keeps suites isolated.
const inbox = await mailqa.inboxes.create({ name: `ci-${process.env.GITHUB_RUN_ID}` });
await registerUser(inbox.address); // your app under test
const message = await mailqa.waitForMessage({
inbox: inbox.id,
subjectContains: 'Verify your email',
timeout: 30_000,
});
expect(message.html).toContain('Welcome');
await page.goto(message.links[0].url);
await mailqa.inboxes.delete(inbox.id); // teardownGetting mail in
Either point your app's SMTP config at the inbox's own credentials:
const inbox = await mailqa.inboxes.create({ name: 'ci' });
// inbox.smtp -> { host, ports: [587, 2525], username, password }…or send to inbox.address ([email protected]) over the public internet.
Both land in the same inbox and read identically through this client.
Waiting
waitForMessage polls until something matches or the timeout expires.
By default no time filter is applied — the email you are waiting for has usually
already been sent by the time you call this, so filtering on "now" would race with it.
Isolation comes from using a fresh inbox per test. When you deliberately reuse an
inbox, pass after:
const t0 = new Date();
await triggerPasswordReset(inbox.address);
await mailqa.waitForMessage({ inbox: inbox.id, after: t0 });Shortcuts for the two most common assertions:
const code = await mailqa.waitForCode({ inbox: inbox.id }); // "483920"
const link = await mailqa.waitForLink({
inbox: inbox.id,
matching: /\/verify\?/,
});On timeout you get a MessageTimeoutError carrying the filter you used — which is
what you need to debug "the email never arrived".
API
| Call | Purpose |
|---|---|
| inboxes.create({ name, address?, projectId? }) | New inbox, with SMTP credentials attached |
| inboxes.list() / inboxes.delete(id) | Manage inboxes |
| inboxes.empty(id) | Delete every message — test teardown |
| messages.list(inboxId, filters) | Cursor-paginated search (to, from, subjectContains, q, tag, unread, since) |
| messages.get(id) | Full message: text, sanitised html, rawHtml, links, codes, headers, attachments, SPF/DKIM/DMARC |
| messages.raw(id) | The original .eml |
| messages.attachment(id) | Attachment bytes |
| messages.markRead(id) / messages.delete(id) | Housekeeping |
Errors are MailQAError with a stable code — branch on that, not the message.
Self-hosting
new MailQA({ apiKey, baseUrl: 'https://mailqa.internal' });Full documentation
- SDK reference — every method, the waiting semantics, error codes, and Playwright/Vitest recipes
- REST API reference — what this client wraps
- Quickstart
