Mailwurf

Sending

Send mail from an inbox, to test how your app handles incoming email.

Inboxes can send as well as receive. Use it when the app under test reads mail: replies to a support ticket, mail-to-import, or inbound parsing. To send your app's own receipts and notifications, see Transactional mail.

const inbox = await mailwurf.createInbox();

const sent = await inbox.send({
  to: "support@staging.acme.com",
  subject: "Order 1042 arrived damaged",
  text: "The box was crushed.",
});

sent.status; // "queued"

The message is sent from inbox.sender_address, which is inbox.address except on a domain with its own mail server: there billing@send.acme.com sends as billing@acme.com. The sender cannot be changed per message.

An inbox on a custom domain can only send once sending is on for that domain. Otherwise the call returns 400 Sending is off for {domain}. Turn it on in the domain settings.

Fields

FieldRequiredMeaning
toyesOne address, or Name <address>.
subjectyesMust not be empty.
texttext or htmlPlain-text body.
htmltext or htmlHTML body.
inReplyTonoId of a received message. The mail is sent as a reply to it.
attachmentsnoUp to 10 files. See Attachments.

Over HTTP the same call is POST /v1/inboxes/{id}/messages, with the reply field named in_reply_to. It returns 202:

{ "id": "outbox_Xk3f9x2Ab7QzLp0W", "status": "queued" }

Between two inboxes

Mail to another Mailwurf inbox is delivered directly, so the recipient can wait for it:

const alice = await mailwurf.createInbox();
const bob = await mailwurf.createInbox();

await alice.send({ to: bob.address, subject: "Hello", text: "ping" });

const mail = await bob.waitFor({ subject: "Hello" });
mail.from; // alice.address

The recipient must be an active inbox. An unknown or expired address on the Mailwurf mail domain returns 404 recipient mailbox not found.

Replies

Reply to a received message, so the app under test sees a threaded answer:

const question = await inbox.waitFor({ subject: "How can we help?" });

await inbox.send({
  to: "support@staging.acme.com",
  subject: `Re: ${question.subject}`,
  text: "The tracking link does not work.",
  inReplyTo: question.id,
});

Attachments

Each file has a filename, its content as bytes (Uint8Array, Buffer) or as a base64 string, and an optional contentType. Without contentType, the type is guessed from the file name.

import { readFile } from "node:fs/promises";

await inbox.send({
  to: "import@staging.acme.com",
  subject: "Orders for October",
  text: "See attached.",
  attachments: [{ filename: "orders.csv", content: await readFile("fixtures/orders.csv") }],
});

Over HTTP, attachments is an array of { "filename", "content", "content_type" } with content base64-encoded. Invalid entries return 400.

Delivery

Sending is asynchronous. The call returns when the message is queued, not when it is delivered.

  • Mail to the internet is delivered to the recipient's mail server and retried for a while when that server is temporarily unavailable.
  • If the receiving server rejects the message, it is marked failed and not retried.
  • The dashboard shows each sent message and its status under the inbox's Mail view. See Read the mail.

For better deliverability to external servers, send from an address on a verified custom domain.

Limits

  • 60 sends per organization per minute. More return 429 too many sends.
  • 5 MiB per message, attachments included. Larger messages return 400.
  • 10 attachments per message.
  • One recipient per send.

On this page