SDK reference — @mailqa/client
The official JavaScript/TypeScript client. It wraps the
REST API and adds the one thing a test suite actually needs:
waiting for an email without writing a polling loop or, worse, a sleep().
npm install --save-dev @mailqa/clientShips ESM and CJS with bundled type declarations, and depends on nothing. It uses the
global fetch, so it needs Node 18+ (or any modern runtime).
This page is in two halves. What follows is the guide — the parts of using this client that are not a single method: how waiting behaves, what the errors mean, and how it fits into a test runner. Below that, under API, is the reference, generated from the source so every signature and type is the one you will actually get.
Constructing a client
import { MailQA } from '@mailqa/client';
const mailqa = new MailQA({ apiKey: process.env.MAILQA_API_KEY! });Every option is in MailQAOptions below; apiKey is the only required
one. Point baseUrl at a local stack when the suite runs against one:
const mailqa = new MailQA({
apiKey: process.env.MAILQA_API_KEY!,
baseUrl: process.env.MAILQA_URL ?? 'http://localhost:3101',
});timeout bounds a single HTTP call; waitForMessage({ timeout }) bounds the whole wait,
which is many calls. Setting the first below the second is fine and normal.
Waiting for mail
Polls the inbox until a message matches, then fetches and returns it in full — bodies, links, codes, headers, attachments. This is the method the package exists for.
const message = await mailqa.waitForMessage({
inbox: inbox.id,
subjectContains: 'Verify your email',
timeout: 30_000,
});The options are in WaitForMessageOptions below: inbox plus
timeout, pollInterval, after, and the same filters messages.list takes.
When several messages match, the newest is returned.
About after, and why there is no default
By the time your test calls waitForMessage, the email it is waiting for has usually
already been sent — often already delivered. Defaulting after to "now" would therefore
filter out the very message being waited for, and the failure would be intermittent and
maddening. So no time filter is applied by default.
Isolation comes from a fresh inbox per test, which is cheap and which you want anyway.
When you deliberately reuse an inbox, pass after explicitly:
const t0 = new Date();
await triggerPasswordReset(user.email);
const message = await mailqa.waitForMessage({ inbox: inbox.id, after: t0 });One wrinkle worth knowing when a wait times out: after is not included in the filter
carried by MessageTimeoutError, so a too-late t0 will not be visible in the error. If a
wait times out with after set, re-list the inbox without it before concluding nothing
arrived.
Searching an inbox
messages.list takes exact filters — to, from, subjectContains, tag — and one
fuzzy one, q:
const hits = await mailqa.messages.list(inbox.id, { q: 'pasword' });
hits.data[0].subject; // "Password reset" — the typo still found it
hits.data[0].score; // 0.681818
hits.data[0].matches?.subject; // [[0, 8]] — the characters to highlightq searches subject, sender and body at once. Every term is prefix-matched, so verif
finds Verification; subject and sender are additionally matched by trigram similarity,
which is what forgives a typo. Body text is matched by token only — a misspelling has to be
in the subject or the sender to be tolerated.
Two consequences worth knowing:
qreorders the page. Matches come back best-first, not newest-first, each carrying ascore. Scores are a sum of weighted signals: compare them within one response, never across two.sort: 'newest'puts the clock back in charge and leavesqa filter.matchestells you what to highlight, per field, as[start, end)ranges into that field's string — sorted, non-overlapping, and safe to walk in one pass. It is the server's answer rather than something to recompute: a match can be a typo away from what was typed, and only the ranking knows which rule admitted the row.
waitForMessage and friends always list with sort: 'newest', q or no q — the message
a test is waiting for is the one that just arrived, not the one that scores best.
Errors
Every non-2xx response becomes a MailQAError:
import { MailQAError, MessageTimeoutError } from '@mailqa/client';
try {
await mailqa.inboxes.create({ name: 'ci' });
} catch (err) {
if (err instanceof MailQAError && err.code === 'quota_exceeded') {
// details: { limit, current }
}
}| Property | |
|---|---|
code |
Stable machine-readable code — branch on this, never on the message |
message |
Human-readable, not stable |
status |
HTTP status; 0 for a network failure |
details |
Whatever the API attached (field errors, quota numbers) |
Codes the client produces itself, on top of the API's:
code |
status |
|
|---|---|---|
request_timeout |
408 | A single HTTP request exceeded the client timeout |
network_error |
0 | DNS, TLS or connection failure — baseUrl is a good first suspect |
message_timeout |
408 | waitForMessage gave up (a MessageTimeoutError) |
no_code_found |
422 | waitForCode found a message but no code |
no_link_found |
422 | waitForLink found a message but no matching link |
Types
All exported, all hand-written rather than generated from the server's internal contracts — so consuming this package never requires a workspace dependency to typecheck.
import type {
Inbox, InboxKind, SmtpCredentials, Message, MessageSummary, AttachmentRef, Link, Page,
ListMessagesOptions, WaitForMessageOptions, MessageSource, AuthVerdict, ParseStatus,
MessageMatches, MatchRange,
} from '@mailqa/client';Message extends MessageSummary with text, html (sanitised), rawHtml (original),
blockedRemoteImages, links, codes, headers, auth, parseStatus, parseError,
cc, replyTo, envelopeTo and attachments. The field-by-field notes — including the
html vs rawHtml distinction and how codes are extracted — are in
the API reference.
Recipes
Playwright
import { test, expect } from '@playwright/test';
import { MailQA } from '@mailqa/client';
const mailqa = new MailQA({ apiKey: process.env.MAILQA_API_KEY! });
test('a new user can verify their email', async ({ page }) => {
const inbox = await mailqa.inboxes.create({ name: `signup-${test.info().testId}` });
await page.goto('/signup');
await page.fill('#email', inbox.address);
await page.click('button[type=submit]');
const link = await mailqa.waitForLink({
inbox: inbox.id,
subjectContains: 'Verify',
matching: /\/verify\?/,
});
await page.goto(link);
await expect(page.getByText('Email verified')).toBeVisible();
await mailqa.inboxes.delete(inbox.id);
});One inbox per test, torn down in the test. Prefer a test.afterEach or a fixture so the
inbox is deleted even when an assertion throws.
Vitest / Jest
import { beforeAll, afterAll, expect, it } from 'vitest';
import { MailQA, type Inbox } from '@mailqa/client';
const mailqa = new MailQA({ apiKey: process.env.MAILQA_API_KEY! });
let inbox: Inbox;
beforeAll(async () => {
inbox = await mailqa.inboxes.create({ name: `unit-${Date.now()}` });
// The organization's sink credentials, the same for every inbox.
process.env.SMTP_HOST = inbox.smtp!.host;
process.env.SMTP_USER = inbox.smtp!.username;
process.env.SMTP_PASS = inbox.smtp!.password;
});
afterAll(() => mailqa.inboxes.delete(inbox.id));
it('sends a receipt with the order total', async () => {
const t0 = new Date();
await placeOrder({ email: inbox.address, total: 4200 });
// Reusing one inbox across tests, so scope the wait to this test's window.
const message = await mailqa.waitForMessage({ inbox: inbox.id, after: t0 });
expect(message.subject).toBe('Your receipt');
expect(message.text).toContain('$42.00');
});The sink files mail by the address it was sent to, which is why the order goes to
inbox.address: sent through the sink, an address on your organization's domain lands in
that inbox, tag and all, exactly as it would over MX. Send to anything else —
customer@example.com, say — and it lands in an inbox of that name instead, created the
first time and listed alongside the others; inboxes.list() finds it by address, and its
kind is EXTERNAL. Nothing is delivered either way.
CI
- run: npm test
env:
MAILQA_API_KEY: ${{ secrets.MAILQA_API_KEY }}Name inboxes after the run (ci-${{ github.run_id }}) so an orphan left by a cancelled job
is identifiable. Inboxes count against the plan's limit, so delete them in teardown; a
scheduled sweep that deletes stale ci-* inboxes is a cheap safety net.
Asserting on exact markup
message.html is sanitised for safe rendering — the markup is not what the sender wrote.
Assert against rawHtml when the exact markup is the thing under test:
expect(message.rawHtml).toContain('<td class="cta">');
expect(message.html).toContain('Verify your email'); // safe-to-render version
expect(message.blockedRemoteImages).toBe(2); // tracking pixels strippedA message that could not be parsed
Parsing runs in a worker, off the SMTP connection, and the message row is written by that
worker after parsing — so a message is never visible half-parsed. Until the parse job
runs, the message simply is not there yet, which is exactly what waitForMessage polls
through.
What you can get is a message whose parse failed: parseStatus === 'FAILED', subject
(unparseable message), empty text and html, and parseError explaining why. The raw
source is still there, so an assertion can fall back to it:
const message = await mailqa.waitForMessage({ inbox: inbox.id });
if (message.parseStatus === 'FAILED') {
console.error(message.parseError);
expect(await mailqa.messages.raw(message.id)).toContain('Subject: Your receipt');
}A complete flow
examples/verify-email-flow.mjs
is a runnable version of
all of this — create, send, wait, assert on the code, the link, the attachment bytes and the
raw .eml, then tear down:
node examples/verify-email-flow.mjsPoint it at a local stack with MAILQA_URL and MAILQA_API_KEY; with the dev seed in place
it runs with neither set.
API
Everything below is generated from packages/sdk/src — the signatures, the types and the
prose are read straight out of the source, so they cannot describe a version of this client
that does not exist.
MailQA
Constructors
Constructor
new MailQA(options): MailQA;Parameters
| Parameter | Type |
|---|---|
options |
MailQAOptions |
Returns
Properties
inboxes
readonly inboxes: {
create: (input) => Promise<Inbox>;
delete: (inboxId) => Promise<void>;
empty: (inboxId) => Promise<{
deleted: number;
}>;
list: () => Promise<Inbox[]>;
};create
create: (input) => Promise<Inbox>;Create an inbox, or return the existing one with the same address.
Safe to call on every run: a second call with the same name does not
fail, it hands back the same inbox with the same id and address. Check
created to tell a fresh inbox from a reused one. Either way smtp
carries the organization's sink credentials, so one call is enough to
configure the mailer under test as well.
Reusing also hands back the mail the last run left behind, which a
waitForMessage matching only on sender or subject will match quite
happily. Pass empty: true to start from nothing — it deletes the
inbox's messages and reports how many in emptied. A brand-new inbox
has nothing to delete, so the flag is safe to leave on:
const inbox = await mailqa.inboxes.create({
name: 'checkout-flow',
empty: true,
});
await triggerTheEmail();
const msg = await mailqa.waitForMessage({ inbox: inbox.id });It is destructive and irreversible, so do not set it on an inbox two
suites share. The non-destructive alternative is to leave the mail in
place and bound the wait instead, with
waitForMessage({ inbox: inbox.id, after: new Date() }).
Parameters
| Parameter | Type | Description |
|---|---|---|
input |
{ address?: string; empty?: boolean; name: string; projectId?: string; } |
- |
input.address? |
string |
Local part of the address. Generated from the name plus a random suffix when omitted — which is what CI usually wants. |
input.empty? |
boolean |
Delete the inbox's existing messages before returning it. |
input.name |
string |
Human label, 1–80 characters. |
input.projectId? |
string |
Defaults to the organization's default project. |
Returns
Promise<Inbox>
delete
delete: (inboxId) => Promise<void>;Delete the inbox. Its messages, attachments and their stored objects go with it, and the address stops existing.
The endpoint reports how many messages were destroyed; this method
discards that and resolves to nothing. Use inboxes.empty when you want
the count.
Parameters
| Parameter | Type |
|---|---|
inboxId |
string |
Returns
Promise<void>
empty
empty: (inboxId) => Promise<{
deleted: number;
}>;Delete every message in the inbox. The teardown call for a test suite.
Parameters
| Parameter | Type |
|---|---|
inboxId |
string |
Returns
Promise<{
deleted: number;
}>
list
list: () => Promise<Inbox[]>;Every inbox in the organization, newest first, without credentials.
Returns
Promise<Inbox[]>
messages
readonly messages: {
attachment: (attachmentId) => Promise<ArrayBuffer>;
delete: (messageId) => Promise<void>;
get: (messageId) => Promise<Message>;
list: (inboxId, options) => Promise<Page<MessageSummary>>;
markRead: (messageId, read) => Promise<MessageSummary>;
raw: (messageId) => Promise<string>;
};attachment
attachment: (attachmentId) => Promise<ArrayBuffer>;Attachment bytes, following the presigned redirect.
Parameters
| Parameter | Type |
|---|---|
attachmentId |
string |
Returns
Promise<ArrayBuffer>
delete
delete: (messageId) => Promise<void>;Delete one message, along with its stored raw source and attachment blobs.
Parameters
| Parameter | Type |
|---|---|
messageId |
string |
Returns
Promise<void>
get
get: (messageId) => Promise<Message>;The full message — bodies, links, codes, headers, attachments.
waitForMessage calls this for you, so you rarely need it
directly.
Parameters
| Parameter | Type |
|---|---|
messageId |
string |
Returns
Promise<Message>
list
list: (inboxId, options) => Promise<Page<MessageSummary>>;A page of summaries, newest first, keyset-paginated on nextCursor.
q is the exception to "newest first": it searches fuzzily — tolerating
typos and half-typed words across subject, sender and body — and returns
the page best-match-first, each summary carrying a score and the
matches to highlight. sort: 'newest' puts the clock back in charge
while keeping q as a filter.
In a test, prefer waitForMessage — it polls this for you
and fails with a filter you can read, rather than an empty page you have
to interpret.
Parameters
| Parameter | Type |
|---|---|
inboxId |
string |
options |
ListMessagesOptions |
Returns
Promise<Page<MessageSummary>>
markRead
markRead: (messageId, read) => Promise<MessageSummary>;Mark a message read, or unread with read: false. Returns the updated
summary.
Parameters
| Parameter | Type | Default value |
|---|---|---|
messageId |
string |
undefined |
read |
boolean |
true |
Returns
Promise<MessageSummary>
raw
raw: (messageId) => Promise<string>;The original .eml, exactly as transmitted.
Parameters
| Parameter | Type |
|---|---|
messageId |
string |
Returns
Promise<string>
smtp
readonly smtp: {
credentials: () => Promise<SmtpCredentials>;
rotate: () => Promise<SmtpCredentials>;
};credentials
credentials: () => Promise<SmtpCredentials>;The organization's SMTP sink credentials: host, ports, username and password.
One set for the whole organization, not one per inbox. Point the app
under test at them and everything it sends is captured and filed by the
address it was sent to: an inbox's own address lands in that inbox,
customer@example.com lands in an inbox of that name, created on first
use. Nothing is delivered.
smtp is on the response from inboxes.create already; this is how to
read it without creating anything. Re-readable by design — a developer
copies these into an app config more than once, and forcing a rotation
each time would be hostile.
Returns
Promise<SmtpCredentials>
rotate
rotate: () => Promise<SmtpCredentials>;Issue a new password for the organization. The old one stops working
immediately, for every mailer configured with it; the username is kept.
Needs an API key or an admin session — a member's session is refused
with forbidden.
Returns
Promise<SmtpCredentials>
Methods
waitForCode()
waitForCode(options): Promise<string>;Wait for a message and return its first extracted verification code.
const code = await mailqa.waitForCode({ inbox: inbox.id }); // "729104"
await page.fill('#otp', code);Throws MailQAError with code no_code_found (422) when the message
arrived but carries no code. Extraction requires a keyword near the
digits, so a bare six-digit run — an order number, say — is deliberately
not treated as one.
Parameters
| Parameter | Type |
|---|---|
options |
WaitForMessageOptions |
Returns
Promise<string>
waitForLink()
waitForLink(options): Promise<string>;Wait for a message and return the first link matching matching — a
substring or a RegExp — or the first link at all when it is omitted.
const link = await mailqa.waitForLink({
inbox: inbox.id,
subjectContains: 'Verify',
matching: //verify\?/,
});
await page.goto(link);Throws MailQAError with code no_link_found (422) when nothing matches.
Parameters
| Parameter | Type |
|---|---|
options |
WaitForMessageOptions & { matching?: string | RegExp; } |
Returns
Promise<string>
waitForMessage()
waitForMessage(options): Promise<Message>;Block until a message matching the filter arrives, then return it in full.
This exists so tests never write await sleep(5000).
It applies no time filter by default — see the comment on after below for
why "since now" would be the wrong default. Isolation between tests comes
from a fresh inbox per test; pass after explicitly when sharing one.
Parameters
| Parameter | Type |
|---|---|
options |
WaitForMessageOptions |
Returns
Promise<Message>
MailQAError
Thrown for any non-2xx response, and for the failures the client detects itself.
try {
await mailqa.inboxes.create({ name: 'ci' });
} catch (err) {
if (err instanceof MailQAError && err.code === 'quota_exceeded') {
// err.details: { limit, current }
}
}Extends
Error
Extended by
Constructors
Constructor
new MailQAError(
code,
message,
status,
details?
): MailQAError;Parameters
| Parameter | Type | Description |
|---|---|---|
code |
string |
Stable machine-readable code — branch on this, never on the message. |
message |
string |
- |
status |
number |
HTTP status, or 0 for a network failure that never got one. |
details? |
unknown |
Whatever the API attached: field errors, quota numbers. |
Returns
Overrides
Error.constructorProperties
code
readonly code: string;Stable machine-readable code — branch on this, never on the message.
details?
readonly optional details?: unknown;Whatever the API attached: field errors, quota numbers.
status
readonly status: number;HTTP status, or 0 for a network failure that never got one.
MessageTimeoutError
Thrown by waitForMessage when nothing matched inside the timeout.
It carries the filter it used, because "no email arrived" is the single most common test failure and the filter is what you need to debug it:
catch (err) {
if (err instanceof MessageTimeoutError) {
console.error(err.filter); // { inbox: 'clx7…', subjectContains: 'Verify' }
console.error(err.timeoutMs); // 30000
}
}Work through it in this order: did the app under test actually send (check
its logs), did it send here (the SMTP credentials, or the recipient
address), and is the filter right (subjectContains is a case-insensitive
substring, not a pattern)? Listing the inbox unfiltered usually settles it
in one call.
Extends
Constructors
Constructor
new MessageTimeoutError(timeoutMs, filter): MessageTimeoutError;Parameters
| Parameter | Type | Description |
|---|---|---|
timeoutMs |
number |
The timeout that elapsed, in ms. |
filter |
Record<string, unknown> |
The filter that matched nothing. Note that after is not included: a too-late after is invisible here, so re-list the inbox without one before concluding nothing arrived. |
Returns
Overrides
Properties
code
readonly code: string;Stable machine-readable code — branch on this, never on the message.
Inherited from
details?
readonly optional details?: unknown;Whatever the API attached: field errors, quota numbers.
Inherited from
filter
readonly filter: Record<string, unknown>;The filter that matched nothing.
Note that after is not included: a too-late after is invisible here,
so re-list the inbox without one before concluding nothing arrived.
status
readonly status: number;HTTP status, or 0 for a network failure that never got one.
Inherited from
timeoutMs
readonly timeoutMs: number;The timeout that elapsed, in ms.
AttachmentRef
Properties
contentId
contentId: string | null;contentType
contentType: string;downloadUrl
downloadUrl: string;filename
filename: string;id
id: string;isInline
isInline: boolean;size
size: number;Inbox
Properties
address
address: string;The full address mail is filed under: qauser@org-one.mailqa.io for an
MX inbox, the recipient the app sent to for an EXTERNAL one.
Once the organization has verified a receiving domain of its own, an MX
inbox is reported on that domain instead — qauser@test.acme.com — since
that is the address it set out to test against. The platform address keeps
working and moves to aliases, so send to whichever you like.
aliases
aliases: string[];Other addresses this same inbox answers at — the platform address, once a receiving domain of the organization's own is in use, and empty otherwise. They are one inbox, not several: mail to any of them lands here.
created?
optional created?: boolean;Only present on the response from inboxes.create: true when this call
made the inbox, false when it already existed and was returned as-is.
createdAt
createdAt: string;createdById
createdById: string | null;The dashboard user who created the inbox, or null when it was created
with an API key, by the SMTP sink, or the creator's account is gone.
Decides who may empty or delete it from the dashboard: a MEMBER only
their own MX inboxes and any EXTERNAL one, ADMIN and OWNER any.
API keys are not role-scoped.
emptied?
optional emptied?: number;Only present when inboxes.create was called with empty: true: how many
messages were deleted. Zero for an inbox this call just made.
id
id: string;isActive
isActive: boolean;kind
kind: InboxKind;messageCount
messageCount: number;name
name: string;projectId
projectId: string;smtp?
optional smtp?: SmtpCredentials;Only present on the response from inboxes.create: the organization's
SMTP credentials, the same for every inbox.
Link
Properties
text
text: string;url
url: string;ListMessagesOptions
Properties
cursor?
optional cursor?: string;from?
optional from?: string;limit?
optional limit?: number;q?
optional q?: string;Fuzzy search across subject, sender and body — typos and half-typed words
included. Unlike the other filters this one also reorders the page:
matches come back best-first, each carrying score and matches. Pass
sort: 'newest' to keep the chronological order and use q as a filter
alone.
since?
optional since?: string | Date;sort?
optional sort?: "relevance" | "newest";Ordering. Defaults to 'relevance' when q is set, 'newest' otherwise.
'relevance' without a q is rejected.
subjectContains?
optional subjectContains?: string;tag?
optional tag?: string;to?
optional to?: string;unread?
optional unread?: boolean;MailQAOptions
Properties
apiKey
apiKey: string;Your organization's API key, mqa_live_…. Required: the constructor
throws immediately when it is empty, rather than failing on the first
request from somewhere deep in a test.
baseUrl?
optional baseUrl?: string;Defaults to https://api.mailqa.io. Point it at a local stack when
running the suite against one. Trailing slashes are trimmed.
fetch?
optional fetch?: {
(input, init?): Promise<Response>;
(input, init?): Promise<Response>;
};Inject your own fetch for instrumentation or a custom agent.
Call Signature
(input, init?): Promise<Response>;
Parameters
| Parameter | Type |
|---|---|
input |
URL | RequestInfo |
init? |
RequestInit |
Returns
Promise<Response>
Call Signature
(input, init?): Promise<Response>;
Parameters
| Parameter | Type |
|---|---|
input |
string | URL | Request |
init? |
RequestInit |
Returns
Promise<Response>
timeout?
optional timeout?: number;Per-request timeout in ms. Default 30 000.
This bounds a single HTTP call. waitForMessage({ timeout }) bounds the
whole wait, which is many calls — setting this below that is normal.
Message
Extends
Properties
attachments
attachments: AttachmentRef[];auth
auth: {
dkim: AuthVerdict;
dmarc: AuthVerdict;
spf: AuthVerdict;
};dkim
dkim: AuthVerdict;dmarc
dmarc: AuthVerdict;spf
spf: AuthVerdict;blockedRemoteImages
blockedRemoteImages: number;cc
cc: string[];codes
codes: string[];envelopeTo
envelopeTo: string | null;from
from: {
address: string;
name: string | null;
};address
address: string;name
name: string | null;Inherited from
hasAttachments
hasAttachments: boolean;Inherited from
headers
headers: [string, string][];html
html: string | null;Sanitised for safe rendering.
id
id: string;Inherited from
inboxId
inboxId: string;Inherited from
isRead
isRead: boolean;Inherited from
links
links: Link[];matches?
optional matches?: MessageMatches;Where the q terms landed, on a q listing only.
Inherited from
parseError
parseError: string | null;parseStatus
parseStatus: ParseStatus;rawHtml
rawHtml: string | null;The original, unsanitised HTML — use when asserting on exact markup.
receivedAt
receivedAt: string;Inherited from
replyTo
replyTo: string | null;score?
optional score?: number;Relevance, on a q listing only. Comparable within one response and
meaningless across two: it is a sum of weighted signals, not a percentage,
and its range moves with the query.
Inherited from
size
size: number;Inherited from
snippet
snippet: string;Inherited from
source
source: MessageSource;Inherited from
subject
subject: string;Inherited from
tag
tag: string | null;Inherited from
text
text: string | null;to
to: string[];Inherited from
MessageMatches
Which characters of a summary the search query hit. Ranges are sorted and never overlap, so wrapping them in markup is one pass over the string.
Properties
fromAddress
fromAddress: MatchRange[];fromName
fromName: MatchRange[];snippet
snippet: MatchRange[];subject
subject: MatchRange[];MessageSummary
Extended by
Properties
from
from: {
address: string;
name: string | null;
};address
address: string;name
name: string | null;hasAttachments
hasAttachments: boolean;id
id: string;inboxId
inboxId: string;isRead
isRead: boolean;matches?
optional matches?: MessageMatches;Where the q terms landed, on a q listing only.
receivedAt
receivedAt: string;score?
optional score?: number;Relevance, on a q listing only. Comparable within one response and
meaningless across two: it is a sum of weighted signals, not a percentage,
and its range moves with the query.
size
size: number;snippet
snippet: string;source
source: MessageSource;subject
subject: string;tag
tag: string | null;to
to: string[];Page
Type Parameters
| Type Parameter |
|---|
T |
Properties
data
data: T[];hasMore
hasMore: boolean;nextCursor
nextCursor: string | null;SmtpCredentials
The organization's SMTP sink credentials — one set, shared by every inbox. Mail sent through them is filed by the address it was sent to.
Properties
host
host: string;password
password: string;ports
ports: number[];username
username: string;WaitForMessageOptions
Extends
Omit<ListMessagesOptions,"limit"|"cursor">
Properties
after?
optional after?: Date;Only match messages received at or after this instant. Unset by default: the message being waited for has often already arrived, so a default time filter would race with it. Use a fresh inbox per test for isolation, or pass this explicitly when reusing one.
from?
optional from?: string;Inherited from
inbox
inbox: string;pollInterval?
optional pollInterval?: number;Gap between polls in ms. Default 1000.
q?
optional q?: string;Fuzzy search across subject, sender and body — typos and half-typed words
included. Unlike the other filters this one also reorders the page:
matches come back best-first, each carrying score and matches. Pass
sort: 'newest' to keep the chronological order and use q as a filter
alone.
Inherited from
since?
optional since?: string | Date;Inherited from
sort?
optional sort?: "relevance" | "newest";Ordering. Defaults to 'relevance' when q is set, 'newest' otherwise.
'relevance' without a q is rejected.
Inherited from
subjectContains?
optional subjectContains?: string;Inherited from
ListMessagesOptions.subjectContains
tag?
optional tag?: string;Inherited from
timeout?
optional timeout?: number;Give up after this many ms. Default 30 000.
to?
optional to?: string;Inherited from
unread?
optional unread?: boolean;Inherited from
AuthVerdict
type AuthVerdict =
| "NONE"
| "PASS"
| "FAIL"
| "SOFTFAIL"
| "NEUTRAL"
| "TEMPERROR"
| "PERMERROR";InboxKind
type InboxKind = "MX" | "EXTERNAL";MX inboxes are the ones you create, with an address on the organization's
mail domain. EXTERNAL ones are created by the SMTP sink, one per address
the app under test sent to (user@gmail.com), the first time mail for it
comes through.
MatchRange
type MatchRange = [number, number];A [start, end) slice of a field, in the units String.prototype.slice takes.
MessageSource
type MessageSource = "INBOUND_MX" | "SMTP_SINK";Public types. Deliberately hand-written rather than imported from the server's internal contracts: this package is published, and consumers must not need a workspace dependency to typecheck.
ParseStatus
type ParseStatus = "PENDING" | "PARSED" | "FAILED";