Compare and switch
Migrate from Mailosaur
Map Mailosaur messages.get, search criteria, codes and links to the InboxSpec wait.
Mailosaur's client waits by searching repeatedly until a message appears. InboxSpec's wait is one request that the server holds open until a message matches or the timeout passes. Most tests change in three places: where the address comes from, the wait call, and how codes and links are read. Mailosaur behaviour described here was checked on 24 October 2026; see the Mailosaur comparison for sources.
Before and after
A Node.js signup test using Mailosaur's client:
import MailosaurClient from "mailosaur";
const mailosaur = new MailosaurClient(process.env.MAILOSAUR_API_KEY);
const serverId = process.env.MAILOSAUR_SERVER_ID;
const emailAddress = `signup-${Date.now()}@${serverId}.mailosaur.net`;
await startSignup(emailAddress); // your application sends the email
const message = await mailosaur.messages.get(
serverId,
{ sentTo: emailAddress, subject: "Verify" },
{ timeout: 30000 },
);
const code = message.html.codes[0].value;
const link = message.html.links[0].href;The same test with InboxSpec's REST API and Node.js 18 or later. Set INBOXSPEC_API_KEY to a project key first.
const origin = process.env.INBOXSPEC_API_URL ?? "https://api.inboxspec.com";
async function inboxspec(path, body) {
const response = await fetch(origin + path, {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.INBOXSPEC_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
// Authentication, quota and validation problems are HTTP errors, never timeouts.
if (!response.ok) throw new Error(`InboxSpec ${response.status}: ${await response.text()}`);
return response.json();
}
const inbox = await inboxspec("/v1/inboxes/for-run", { run_id: `signup-${Date.now()}` });
await startSignup(inbox.address); // your application sends the email
const result = await inboxspec("/v1/messages/wait", {
inbox: inbox.address,
match: { subject_contains: "Verify", has_code: true },
timeout_ms: 30000,
});
if (result.status === "timeout") throw new Error(result.hint);
const code = result.message.codes[0];
const link = result.message.links.find((candidate) => candidate.kind === "verify")?.href;Register the inbox before your application sends to it. Mail to an address that was never registered is not stored.
Call mapping
| Mailosaur | InboxSpec |
|---|---|
| API key as the Basic-auth username | Authorization: Bearer with a project key |
| A server, with any address under its subdomain | A project, with one registered inbox per run from POST /v1/inboxes/for-run |
messages.get(serverId, criteria, options) | POST /v1/messages/wait with inbox, match and timeout_ms |
sentTo | The inbox itself, or match.to_contains |
sentFrom | match.from_contains |
subject | match.subject_contains |
body | match.body_contains, which searches the subject and plain-text part only |
match: "ANY" | No equivalent. Match fields are always combined with AND, so issue one wait per alternative |
receivedAfter, which defaults to one hour ago | match.since, which has no default; every stored message in the inbox is eligible |
timeout, 10 seconds by default, counted in the client | timeout_ms, 120,000 by default and at most 285,000, counted on the server |
errorOnTimeout: false | Always on: a timeout returns status: "timeout" with a hint |
message.html.codes[0].value | result.message.codes[0], best candidate first |
message.html.links[0].href | result.message.links, each with href, text and kind |
message.html.body | GET /v1/messages/:id/body?format=html |
messages.search and messages.list | GET /v1/messages?inbox=… |
messages.del and messages.deleteAll | DELETE /v1/messages/:id, DELETE /v1/inboxes/:id or POST /v1/inboxes/cleanup |
| SMTP settings from the inbox's Connect tab | Capture SMTP credentials, port 587 with STARTTLS |
Behaviour to check after switching
- A wait does not consume messages, and it returns the oldest match. Use a fresh run ID for each journey rather than relying on a time window. See Inboxes.
- Code values are ranked candidates. Assert the one your application issued; see OTP and link assertions.
- Keep your HTTP client's timeout longer than
timeout_ms, so the server's answer arrives before your client gives up. - InboxSpec does not include Mailosaur's compatibility, accessibility, SMS or preview testing. Keep those tests where they are.
Next, follow the CI guide for fixtures, assertions and cleanup in a pipeline.