MailSlurp logo

guides

Send Emails Programmatically in API Workflows and Tests

Learn how to send email from code using MailSlurp. Cover recipients, HTML, attachments, templates, queues, scheduling, verification, and bounce-safe delivery.

You can send email from any MailSlurp inbox you create in the dashboard or API. This guide covers the practical sending paths teams use in production: direct API sends, recipient controls, attachment handling, template sends, and delivery protections.

Free plans have destination restrictions for some common consumer domains (for example Gmail and Yahoo) to reduce abuse risk. See free-plan limits when validating your test matrix.

Quick start

The smallest possible send call only needs an inbox and at least one recipient:

await mailslurp.sendEmail(inboxId, {
  to: [recipientEmail],
});

That pattern is enough for smoke tests and low-friction integration checks. For release-grade delivery, add headers, template variables, verification, and queue controls.

Choose your sending path

Use this rule of thumb before adding complexity:

  • API send first: best default for deterministic test automation and observability.
  • SMTP send when required: useful when validating an application that already sends over SMTP.
  • Template send for transactional workflows: password reset, OTP, notifications, and account lifecycle mail.

If your team is deciding between transports, use SMTP vs HTTP email APIs.

Sender identity and reply behavior

MailSlurp uses the inbox address as the default from and replyTo. You can override headers, but most teams should keep sender identity stable and set display name on the inbox for better deliverability and trust.

await mailslurp.sendEmail(inbox.id, {
  to: [recipientEmail],
  // can include names in addresses in the format below
  replyTo: '"Support" <support@my-app.com>',
  // quote the property if a reserved word
  ["from"]: inbox.emailAddress,
});

Recipients: to, cc, and bcc

Send calls support recipient arrays. Keep recipient generation explicit in tests so failures are easy to trace.

await mailslurp.sendEmail(inbox.id, {
  to: ["user1@example.com"],
  cc: ["manager@example.com"],
  bcc: ["audit@example.com"],
  from: [`"QA Bot" <${inbox.emailAddress}>`],
});

Recipient formats:

  • Plain: user@example.com
  • Named: "Team Ops" <team@example.com>

For localized addresses, use internationalized email guidance.

Subject and content

Use meaningful subjects so test assertions and support triage can classify traffic quickly.

await mailslurp.sendEmail(inbox.id, {
  to: ["me@test.com"],
  subject: "Order confirmation #1452",
  body: "Your order has shipped.",
});

For HTML emails, set html: true and keep a text equivalent where possible.

await mailslurp.sendEmail(inbox.id, {
  to: ["me@test.com"],
  subject: "Welcome",
  body: "<h1>Welcome</h1><p>Your account is ready.</p>",
  html: true,
  charset: "utf8",
});

Attachments

Upload file content first, then pass attachment IDs in your send call.

// first upload attachments as base64 encoded string
const fs = require("fs");
const file = {
  base64Contents: Buffer.from(
    fs.readFileSync(attachmentPath, { encoding: "base64" }),
  ).toString(),
  contentType: "image/png",
  filename: "pixel.png",
};
const [attachmentId] = await mailslurp.uploadAttachment(file);

// send email with attachment ID in array
await mailslurp.sendEmail(inbox.id, {
  to: [emailAddress],
  attachments: [attachmentId],
});

In Node.js, encode binary files to base64 before upload:

import { readFileSync } from "fs";

const bytes = readFileSync(path);
const base64Contents = Buffer.from(bytes).toString("base64");

Bounce and deliverability protection

Delivery quality depends on recipient hygiene and sender behavior. Use these controls in production pipelines:

  • Validate recipients before high-volume sends.
  • Filter known-bad recipients at send time.
  • Track bounces and complaints with webhooks.
  • Gate release jobs when rejection rates spike.

Filter bounced recipients automatically:

// use the `filterBouncedRecipients` to filter
// out previously bounced recipients and avoid errors
await mailslurp.sendEmail(inboxId, {
  to: [recipient, bouncedRecipient],
  filterBouncedRecipients: true,
});
// will only email `recipient` and will skip a known bounced recipient

Pre-validate email addresses:

const verificationResult = await mailslurp.emailVerificationController.validateEmailAddressList(
  {
    validateEmailAddressListOptions: {
      emailAddressList: [recipient],
    },
  },
);
// returns a map of `{ emailAddress: isValid }`
expect(verificationResult.resultMapEmailAddressIsValid[recipient]).toBeTruthy();
// or lookup results in arrays
expect(verificationResult.invalidEmailAddresses.length).toEqual(0);
expect(verificationResult.validEmailAddresses.length).toEqual(1);

Validate during send and enforce policy (filter or fail):

// the `validateEmailAddresses` option will verify then filter and remove bad recipients
// you can also configure the method to throw instead with `VALIDATE_ERROR_IF_INVALID`
await mailslurp.sendEmail(inboxId, {
  to: [recipient, bouncedRecipient],
  validateEmailAddresses:
    SendEmailOptionsValidateEmailAddressesEnum.VALIDATE_FILTER_REMOVE_INVALID,
});

For incident response patterns, review reduce email bounces with webhooks and deliverability testing.

Queue-backed and scheduled sending

When a workflow must tolerate transient failures, use queue-backed sends and retries instead of synchronous-only delivery.

Queue-backed sending:

await mailslurp.inboxController.sendEmailWithQueue({
  inboxId: inboxId,
  sendEmailOptions: {
    to: [recipient],
    subject: "Sent with a queue",
    body:
      "Use queues to allow recovery of failed email " +
      "sending when account reaches limits or has payment issues",
  },
  // validate before adding to queue to fail early
  validateBeforeEnqueue: false,
});

Send later using schedule controls:

await mailslurp.inboxController.sendWithSchedule({
  inboxId: inboxId,
  sendEmailOptions: {
    to: [recipient],
    subject: "Send with schedule",
    body: "Scheduled email",
  },
  validateBeforeEnqueue: true,
  sendAtNowPlusSeconds: 5,
});

See send email later for delayed and batch scheduling patterns.

Templates and variables

Templates are useful when product, QA, and support teams need a shared source of truth for email bodies.

Create a template:

await mailslurp.templateController.createTemplate({
  createTemplateOptions: {
    name: "Welcome",
    content: "Welcome to our app",
  },
});

Define variables with handlebars syntax:

const template = await mailslurp.templateController.createTemplate({
  createTemplateOptions: {
    name: "Welcome email",
    content: "Hello {{firstName}}. Welcome to {{brandName}}.",
  },
});
expect(template.id).toBeTruthy();
expect(template.variables).toEqual([
  {
    name: "firstName",
    variableType: TemplateVariableVariableTypeEnum.STRING,
  },
  {
    name: "brandName",
    variableType: TemplateVariableVariableTypeEnum.STRING,
  },
]);

Send with runtime values:

const templateObject = await mailslurp.templateController.getTemplate({
  templateId,
});
expect(templateObject.content).toContain("Hello {{firstName}}. Welcome to {{brandName}}.");
const sent = await mailslurp.sendEmail(inboxId, {
  to: [emailAddress],
  subject: "Welcome {{firstName}}",
  template: templateId,
  templateVariables: {
    firstName: "Sally",
    brandName: "My Company",
  } as any,
});

MailSlurp validates missing or mismatched variables, which helps prevent broken releases.

Send to contacts and groups

If you maintain audience objects in MailSlurp, use contact and group sending primitives:

await mailslurp.sendEmail(inbox.id, { toContacts: [contactId1, contactId2] });
await mailslurp.sendEmail(inbox.id, { toGroup: groupId });

Practical checklist before production rollout

  • Verify SPF, DKIM, and DMARC for your sender domain.
  • Test the full flow in Email Sandbox before sending from your app.
  • Add webhooks for delivery, bounce, and complaint events.
  • Run integration email tests for signup, reset, and notification paths.
  • Keep a recipient validation and suppression strategy in place.

Next steps