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/client

Ships 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 highlight

q 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:

  • q reorders the page. Matches come back best-first, not newest-first, each carrying a score. Scores are a sum of weighted signals: compare them within one response, never across two. sort: 'newest' puts the clock back in charge and leaves q a filter.
  • matches tells 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 stripped

A 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.mjs

Point 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

MailQA

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(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

MailQAError

Overrides
Error.constructor

Properties

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

MessageTimeoutError

Overrides

MailQAError.constructor

Properties

code

readonly code: string;

Stable machine-readable code — branch on this, never on the message.

Inherited from

MailQAError.code

details?

readonly optional details?: unknown;

Whatever the API attached: field errors, quota numbers.

Inherited from

MailQAError.details

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

MailQAError.status

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.


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>;

MDN Reference

Parameters
Parameter Type
input URL | RequestInfo
init? RequestInit
Returns

Promise<Response>

Call Signature
(input, init?): Promise<Response>;

MDN Reference

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

MessageSummary.from

hasAttachments

hasAttachments: boolean;
Inherited from

MessageSummary.hasAttachments

headers

headers: [string, string][];

html

html: string | null;

Sanitised for safe rendering.

id

id: string;
Inherited from

MessageSummary.id

inboxId

inboxId: string;
Inherited from

MessageSummary.inboxId

isRead

isRead: boolean;
Inherited from

MessageSummary.isRead

links: Link[];

matches?

optional matches?: MessageMatches;

Where the q terms landed, on a q listing only.

Inherited from

MessageSummary.matches

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

MessageSummary.receivedAt

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

MessageSummary.score

size

size: number;
Inherited from

MessageSummary.size

snippet

snippet: string;
Inherited from

MessageSummary.snippet

source

source: MessageSource;
Inherited from

MessageSummary.source

subject

subject: string;
Inherited from

MessageSummary.subject

tag

tag: string | null;
Inherited from

MessageSummary.tag

text

text: string | null;

to

to: string[];
Inherited from

MessageSummary.to


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

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

ListMessagesOptions.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

ListMessagesOptions.q

since?

optional since?: string | Date;
Inherited from

ListMessagesOptions.since

sort?

optional sort?: "relevance" | "newest";

Ordering. Defaults to 'relevance' when q is set, 'newest' otherwise. 'relevance' without a q is rejected.

Inherited from

ListMessagesOptions.sort

subjectContains?

optional subjectContains?: string;
Inherited from

ListMessagesOptions.subjectContains

tag?

optional tag?: string;
Inherited from

ListMessagesOptions.tag

timeout?

optional timeout?: number;

Give up after this many ms. Default 30 000.

to?

optional to?: string;
Inherited from

ListMessagesOptions.to

unread?

optional unread?: boolean;
Inherited from

ListMessagesOptions.unread


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";
Something wrong or missing? Edit this page on GitHub.