Appearance
Node.js Guide
If you haven't already, see Getting Started. The following examples will assume you have already received your API key and created a namespace.
mailisk-node
Mailisk offers a Node.js library mailisk-node. This is an official library that wraps the API Reference endpoints into functions.
See the README for more usage examples.
Installation
First install the library using npm
shell
npm install --save-dev mailiskOr yarn
shell
yarn add mailisk --devSetup Client
Once installed import the library and create the client by passing in your API key
js
const { MailiskClient } = require("mailisk");
const mailisk = new MailiskClient({ apiKey: "YOUR_API_KEY" });Reading Email
You can read emails using the searchInbox function.
- By default it uses the
waitflag. This means the call won't return until at least one email is received. Disabling this flag viawait: falsecan cause it to return an empty response immediately. - The request timeout is adjustable by passing
timeoutin the request options. By default it uses a timeout of 5 minutes. - By default it returns emails in the last 15 minutes. This ensures that only new emails are returned. Without this, older emails would also be returned, potentially disrupting you if you were waiting for a specific email. This can be overridden by passing the
from_timestampparameter (from_timestamp: 0will disable filtering by email age).
js
// timeout of 5 minutes
await mailisk.searchInbox(namespace);
// timeout of 1 minute
await mailisk.searchInbox(namespace, {}, { timeout: 1000 * 60 });
// returns immediately, even if the result would be empty
await mailisk.searchInbox(namespace, { wait: false });The response will look similar to this:
json
{
"total_count": 1,
"options": {
"limit": 10,
"offset": 0
},
"data": [
{
"id": "1659368409795-42UcuQtMy",
"from": {
"address": "contact@mailisk.com",
"name": ""
},
"to": [
{
"address": "john@sh7o3t3e4jvj.mailisk.net",
"name": ""
}
],
"subject": "Test - Welcome to Mailisk 👋",
"html": "<html>...",
"text": "*Welcome to Mailisk*\n\nHello there 👋\nWelcome to Mailisk! We're excited to have you!\n\nGo ahead and ...",
"received_date": "2022-08-01T15:40:09.000Z",
"received_timestamp": 1659368409,
"expires_timestamp": 1659372009
}
]
}The total_count tells us the total number of emails that match our query, depending on limit and offset a subset of this will be returned. See the Search Inbox Response for more information on the other fields.
Sending Email
Mailisk supports sending an email using Virtual SMTP. This will fetch the SMTP settings for the selected namespace and send an email. These emails can only be sent to an address that ends in @{namespace}.mailisk.net.
js
const namespace = "mynamespace";
await mailisk.sendVirtualEmail(namespace, {
from: "test@example.com",
to: `john@${namespace}.mailisk.net`,
subject: "This is a test",
text: "Testing",
});Request SMS Numbers
Request and activate an SMS number from the dashboard (see SMS Testing). Surface it to your Node tests via configuration, e.g. process.env.MAILISK_SMS_NUMBER, so your specs all hit the same destination.
Listing SMS Numbers
Surface the list of approved numbers tied to your API key with listSmsNumbers(). This is useful for one-time setup scripts or sanity checks in CI before running OTP flows.
js
const { data: smsNumbers } = await mailisk.listSmsNumbers();
if (!smsNumbers.length) throw new Error("No SMS numbers available");
const activeNumber = smsNumbers.find((num) => num.status === "active");
if (!activeNumber) throw new Error("Activate an SMS number before running tests");
console.log(`Using SMS number ${activeNumber.phone_number}`);Reading SMS
Use searchSmsMessages(phoneNumber, filters?, options?) to poll for inbound texts. It follows the same defaults as searchInbox:
waitdefaults totrue, holding the request open until at least one SMS matches the filters.- The third
optionsargument lets you override the 5 minute timeout to handle slower OTP flows. - Filter arguments align with the Search SMS API:
body,from_number,from_date,to_date,limit,offset, etc. Date filters such asfrom_dateandto_dateshould be ISO timestamp strings.
js
const smsNumber = process.env.MAILISK_SMS_NUMBER;
const { data: smsMessages } = await mailisk.searchSmsMessages(
smsNumber,
{
body: "Your login code",
},
{
timeout: 1000 * 120,
}
);
if (!smsMessages.length) throw new Error("SMS not received in time");
const sms = smsMessages[0];
const otp = sms.body.match(/(\d{6})/)[1];If you need to reuse the same number across test runs, store run-specific state in your application (user IDs, tenants, etc.) and filter using the message body. Alternatively, avoid running parallel tests that share the same number, and set from_date to an ISO timestamp string to exclude messages created before the current test run.
An example with test Playwright is available here.
Sending Virtual SMS
To simulate inbound SMS (useful for previewing one-off notifications or driving tests without your production provider), call sendVirtualSms. Provide both numbers in E.164 or local dial format; Mailisk will deliver the SMS into the destination number you own.
js
await mailisk.sendVirtualSms({
from_number: "+15551234567",
to_number: process.env.MAILISK_SMS_NUMBER,
body: "Your login code is 123456",
});Virtual SMS do not consume your regular inbound quota and land in the same search results as provider-delivered messages, so you can keep using searchSmsMessages to assert content.
Authenticator TOTP
The Node.js client can generate authenticator app one-time passwords and manage saved virtual TOTP devices. The SDK calls the Mailisk API for code generation and device storage; it does not generate TOTP codes locally.
TOTP methods require an organisation API key, prefixed with sk_org_. User API keys are rejected for these endpoints.
js
const { MailiskClient } = require("mailisk");
const mailisk = new MailiskClient({
apiKey: process.env.MAILISK_ORGANISATION_API_KEY,
});Use the flow that matches your test setup:
| Use case | Method |
|---|---|
| Generate a current code from a known Base32 secret without saving anything | getTotpOtpBySharedSecret(sharedSecret) |
| Save a reusable device from a Base32 shared secret with default settings | createTotpDevice(params) |
| Save a reusable device with custom TOTP settings | createCustomTotpDevice(params) |
| Save a device from a Base32 secret-key field | createTotpDeviceFromBase32SecretKey(params) |
Save a device from an otpauth://totp/... setup URL | createTotpDeviceFromOtpAuthUrl(params) |
| Generate a code for a saved device | getTotpOtpByDeviceId(deviceId) |
| List or delete saved devices | listTotpDevices(params?), deleteTotpDevice(deviceId) |
Generate a code without saving a device
Use this when your test already has the shared secret and does not need saved-device management.
js
const otp = await mailisk.getTotpOtpBySharedSecret("JBSWY3DPEHPK3PXP");
await page.fill("[name='otp']", otp.code);
console.log(`Code expires at ${otp.expires}`);This method uses the default TOTP settings: 6 digits, a 30 second period, and SHA1.
Save a device and reuse it later
Use createTotpDevice for a saved device with the default TOTP settings.
js
const device = await mailisk.createTotpDevice({
name: "GitHub staging",
sharedSecret: "JBSWY3DPEHPK3PXP",
expiresAt: "2026-06-01T12:00:00.000Z",
});
const otp = await mailisk.getTotpOtpByDeviceId(device.id);
await page.fill("[name='otp']", otp.code);Use createCustomTotpDevice when the application under test uses non-default settings or when you want issuer and username metadata on the saved device.
js
const device = await mailisk.createCustomTotpDevice({
name: "GitHub staging",
secret: "JBSWY3DPEHPK3PXP",
username: "qa@example.com",
issuer: "GitHub",
digits: 6,
period: 30,
algorithm: "SHA1",
});
const otp = await mailisk.getTotpOtpByDeviceId(device.id);Create a device from a Base32 secret key
Some test setup flows expose the shared secret as a Base32 secret key. Use createTotpDeviceFromBase32SecretKey when that field name matches your integration.
js
const device = await mailisk.createTotpDeviceFromBase32SecretKey({
base32SecretKey: "JBSWY3DPEHPK3PXP",
username: "qa@example.com",
issuer: "GitHub",
});Create a device from an otpauth URL
Use createTotpDeviceFromOtpAuthUrl when your application exposes the authenticator setup URL directly or through a QR-code payload.
js
const device = await mailisk.createTotpDeviceFromOtpAuthUrl({
otpAuthUrl: "otpauth://totp/GitHub:qa@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub",
name: "GitHub staging",
});
const otp = await mailisk.getTotpOtpByDeviceId(device.id);The URL must include a secret query parameter. Values inside the URL are used for fields such as issuer, username, digits, period, and algorithm when present.
List and delete saved devices
listTotpDevices returns active, non-expired devices for the organisation. issuer and username filters are case-insensitive partial matches.
js
const result = await mailisk.listTotpDevices({
limit: 20,
offset: 0,
issuer: "GitHub",
username: "qa@example.com",
});
console.log(result.total_count);
console.log(result.items);Delete saved devices when they are no longer needed.
js
await mailisk.deleteTotpDevice(device.id);Successful deletes return no response body.
Supported TOTP settings
Default settings:
| Setting | Default |
|---|---|
digits | 6 |
period | 30 seconds |
algorithm | SHA1 |
Supported custom values:
| Field | Accepted values |
|---|---|
digits | 6 or 8 |
period | Integer from 10 to 300 seconds |
algorithm | SHA1, SHA256, or SHA512 |
Secrets are write-only. The API accepts shared secrets when creating devices or generating codes, but saved device responses do not include the secret.
Response shapes
Saved device responses use this shape:
ts
type TotpDevice = {
id: string;
organisation_id: string;
name: string;
username?: string | null;
issuer?: string | null;
digits: number;
period: number;
algorithm: "SHA1" | "SHA256" | "SHA512";
source: "shared_secret" | "custom" | "base32_secret_key" | "otpauth_url" | string;
expiresAt?: string | null;
created_at: string;
updated_at: string;
};OTP responses include the current code and an ISO timestamp for when that code expires:
ts
type TotpOtpResponse = {
code: string;
expires: string;
};For REST request examples and endpoint-level errors, see the Authenticator TOTP API reference.
Breaking changes
- With version v2.0.0 the default
from_timestamphas been changed from the past 5 seconds to the past 15 minutes. Since this can break existing tests a new major version has been released.
