Forwarding

Forwarding

Inbox forwarding allows you to create rules that match inbound emails and forward them automatically to other email addresses.

Find forwarders in dashboard

You can create and manage inbox forwarders in the MailSlurp dashboard or using the API.

Click inboxes1. Click inboxes
Click forwarders2. Click forwarders
Or access via the inbox page3. Or access via the inbox page

How forwarding works

Inbox forwarding rules can be attached to inboxes and catch-all inboxes and are applied to inbound emails. Rules can match on email fields such as subject, or sender, and can be used to forward emails to other email addresses.

When an inbox with a forwarding rule attached receives an inbound email the rules are evaluated. For each matching rule the email is forwarded to the attached rule recipient. Both the original inbox and the recipient inbox receive the inbound email.

Creating inbox forwarders

You can create inbox forwarders in the dashboard or using the API.

create inbox auto forwarder

Matching inbound emails

You can create rules to match on a variety of email properties:

typescript
const forwarderFields = [
CreateInboxForwarderOptionsFieldEnum.SENDER,
CreateInboxForwarderOptionsFieldEnum.RECIPIENTS,
CreateInboxForwarderOptionsFieldEnum.ATTACHMENTS,
CreateInboxForwarderOptionsFieldEnum.SUBJECT
]

At minimum, provide:

  • one match strategy (field + match, or matchOptions), and
  • one or more forwardToRecipients.

To create a basic forwarder in code:

typescript
await mailslurp.inboxForwarderController.createNewInboxForwarder({
inboxId: inbox2.id,
createInboxForwarderOptions: {
field: CreateInboxForwarderOptionsFieldEnum.SENDER,
match: inbox1.emailAddress!,
forwardToRecipients: [inbox3.emailAddress!],
},
});

This pattern is ideal for simple mailbox workflows where one condition controls one destination list.

You can use should to control how matching is done:

  • WILDCARD: wildcard matching such as Invoice*.
  • MATCH: regex matching.
  • CONTAIN: case-insensitive contains.
  • EQUAL: normalized equality.

CONTAIN and EQUAL examples

Use CONTAIN for partial string matching and EQUAL for exact normalized matches.

typescript
await inboxForwarderController.createNewInboxForwarder({
inboxId,
createInboxForwarderOptions: {
field: 'SUBJECT',
should: 'CONTAIN',
match: 'security alert',
forwardToRecipients: ['ops@example.com'],
},
});
await inboxForwarderController.createNewInboxForwarder({
inboxId,
createInboxForwarderOptions: {
field: 'SENDER',
should: 'EQUAL',
match: 'billing@example.com',
forwardToRecipients: ['finance@example.com'],
},
});

Use CONTAIN when subjects may include prefixes/suffixes (for example timestamps). Use EQUAL when sender identity must be exact.

Wildcard matching

You can use an * asterisk to match all characters after a given pattern.

typescript
await mailslurp.inboxForwarderController.createNewInboxForwarder({
inboxId: inbox1.id,
createInboxForwarderOptions: {
field: CreateInboxForwarderOptionsFieldEnum.SUBJECT,
match: 'Invoice AB-*',
forwardToRecipients: [inbox2.emailAddress!],
},
});

Test the wildcard rule by sending an email and waiting for the forwarded copy:

typescript
const sent = await mailslurp.inboxController.sendEmailAndConfirm({
inboxId: inbox3.id!!,
sendEmailOptions: {
to: [inbox1.emailAddress!!],
subject: 'Invoice AB-123',
},
});
typescript
// see that inbox1 gets the original email
const receivedEmail = await mailslurp.waitController.waitForLatestEmail({
inboxId: inbox1.id,
timeout: 60000,
unreadOnly: true,
});
expect(receivedEmail.subject).toContain('Invoice AB-123');
// see that inbox2 also gets a forwarded email
const forwardedEmail = await mailslurp.waitForLatestEmail(
inbox2.id,
60000,
true
);
expect(forwardedEmail.subject).toContain('Invoice AB-123');

This is a good fit when you have stable prefixes like Invoice*, Alert*, or ticket patterns where strict regex is unnecessary.

Regex pattern matching (should=MATCH)

Use regex when you need strict shape validation (for example OTP formats):

typescript
const patternForwarder = await forwarderController.createNewInboxForwarder({
inboxId: sourceInbox.id,
createInboxForwarderOptions: {
field: 'SUBJECT',
should: 'MATCH',
match: '^OTP code [0-9]{6}$',
forwardToRecipients: [destinationInbox.emailAddress!],
},
});
typescript
await inboxController.sendEmailAndConfirm({
inboxId: destinationInbox.id!,
sendEmailOptions: {
to: [sourceInbox.emailAddress!],
subject: 'OTP code 654321',
body: 'verification',
},
});
typescript
const forwardedEmail = await waitController.waitForLatestEmail({
inboxId: destinationInbox.id!,
timeout: 60_000,
unreadOnly: true,
});
typescript
const patternSuccessEvent = await waitForForwarderSuccessEvent(
patternForwarder.id!,
);

Anchor regexes with ^ and $ when you want full-value matching and avoid unintended partial matches.

Combined rules with nested AND / OR

You can combine:

  • a simple top-level field + match + should rule, and
  • matchOptions for nested boolean logic.

Both must match when provided together.

typescript
const combinedForwarder = await forwarderController.createNewInboxForwarder(
{
inboxId: sourceInbox.id,
createInboxForwarderOptions: {
field: 'SUBJECT',
should: 'CONTAIN',
match: 'alert',
forwardToRecipients: [destinationInbox.emailAddress!],
matchOptions: {
operator: 'AND',
matches: [
{
field: 'SENDER',
should: 'EQUAL',
value: destinationInbox.emailAddress!,
},
],
groups: [
{
operator: 'OR',
matches: [
{ field: 'SUBJECT', should: 'CONTAIN', value: 'critical' },
{ field: 'SUBJECT', should: 'CONTAIN', value: 'warning' },
],
},
],
},
},
},
);
typescript
await inboxController.sendEmailAndConfirm({
inboxId: destinationInbox.id!,
sendEmailOptions: {
to: [sourceInbox.emailAddress!],
subject: 'critical alert: cpu high',
body: 'threshold exceeded',
},
});
typescript
const forwardedEmail = await waitController.waitForLatestEmail({
inboxId: destinationInbox.id!,
timeout: 60_000,
unreadOnly: true,
});
typescript
const combinedSuccessEvent = await waitForForwarderSuccessEvent(
combinedForwarder.id!,
);

Use this for production triage flows where one weak signal is not enough, such as sender + keyword + one-of category checks.

Attachment-aware forwarding

Forwarders support attachment-aware fields:

  • ATTACHMENT_FILENAME: match on attachment names.
  • ATTACHMENT_TEXT: extract attachment text and match on extracted content.

Attachment filename matching

Use ATTACHMENT_FILENAME when filename itself is enough.

typescript
await inboxForwarderController.createNewInboxForwarder({
inboxId,
createInboxForwarderOptions: {
field: 'ATTACHMENT_FILENAME',
should: 'MATCH',
match: '.*\\.pdf$',
forwardToRecipients: ['archive@example.com'],
},
});

This is useful for deterministic document routing when filenames are generated by upstream systems.

For ATTACHMENT_TEXT, you can set attachmentTextExtractionMethod:

  • AUTO
  • NATIVE (default)
  • OCR
  • LLM
  • OCR_THEN_LLM
typescript
await inboxForwarderController.createNewInboxForwarder({
inboxId,
createInboxForwarderOptions: {
field: 'ATTACHMENT_TEXT',
should: 'CONTAIN',
match: 'order-991',
forwardToRecipients: ['ops@example.com'],
attachmentTextExtractionMethod: 'OCR_THEN_LLM',
},
});

Recommended baseline:

  • start with NATIVE for machine-readable text files and PDFs,
  • move to OCR for image-heavy scans,
  • use LLM or OCR_THEN_LLM for complex layouts and semi-structured docs.

Attachment text matching with native extraction

This flow is deterministic and usually fastest for plain text attachments and text-first PDFs.

typescript
const attachmentTextForwarder =
await forwarderController.createNewInboxForwarder({
inboxId: sourceInbox.id,
createInboxForwarderOptions: {
field: 'ATTACHMENT_TEXT',
should: 'CONTAIN',
match: 'invoice-7788',
forwardToRecipients: [destinationInbox.emailAddress!],
attachmentTextExtractionMethod: 'NATIVE',
} as any,
});
typescript
await inboxController.sendEmailAndConfirm({
inboxId: destinationInbox.id!,
sendEmailOptions: {
to: [sourceInbox.emailAddress!],
subject: 'Invoice attached',
body: 'Please see attachment',
attachments: attachmentIds,
},
});
typescript
const forwardedEmail = await waitController.waitForLatestEmail({
inboxId: destinationInbox.id!,
timeout: 60_000,
unreadOnly: true,
});
typescript
const attachmentTextSuccessEvent = await waitForForwarderSuccessEvent(
attachmentTextForwarder.id!,
);

AI/LLM-based attachment matching

If your plan includes AI features, use attachmentTextExtractionMethod: 'LLM' for harder documents.

typescript
const aiMatchForwarder = await forwarderController.createNewInboxForwarder({
inboxId: sourceInbox.id,
createInboxForwarderOptions: {
field: 'ATTACHMENT_TEXT',
should: 'CONTAIN',
match: 'llm-forward-key-42',
forwardToRecipients: [destinationInbox.emailAddress!],
attachmentTextExtractionMethod: 'LLM',
} as any,
});
typescript
await inboxController.sendEmailAndConfirm({
inboxId: destinationInbox.id!,
sendEmailOptions: {
to: [sourceInbox.emailAddress!],
subject: 'LLM extraction check',
body: 'Use AI extraction for this attachment',
attachments: attachmentIds,
},
});
typescript
const forwardedEmail = await waitController.waitForLatestEmail({
inboxId: destinationInbox.id!,
timeout: 90_000,
unreadOnly: true,
});
typescript
const aiMatchSuccessEvent = await waitForForwarderSuccessEvent(
aiMatchForwarder.id!,
);

LLM extraction is best for noisy or irregular documents. It may add latency and token-based costs compared with native extraction.

Non-match behavior

When an email does not match a forwarder rule:

  • no forwarded email is sent, and
  • no SUCCESS forwarder event is recorded for that email.
typescript
const nonMatchForwarder = await forwarderController.createNewInboxForwarder(
{
inboxId: sourceInbox.id,
createInboxForwarderOptions: {
field: 'SUBJECT',
should: 'MATCH',
match: '^invoice [0-9]{4}$',
forwardToRecipients: [destinationInbox.emailAddress!],
},
},
);
typescript
await inboxController.sendEmailAndConfirm({
inboxId: destinationInbox.id!,
sendEmailOptions: {
to: [sourceInbox.emailAddress!],
subject: 'not-an-invoice-subject',
body: 'should not forward',
},
});
typescript
const events = await forwarderController.getInboxForwarderEvents({
id: nonMatchForwarder.id!,
});

Including negative tests prevents silent over-forwarding and helps validate that your matching logic is specific enough.

Tracking forwarding events

Each forwarding event is recorded and can be accessed via API or dashboard. Use events to audit match behavior, measure forwarding volume, and troubleshoot rules.

Forwarder events include:

  • status (SUCCESS or FAILURE),
  • related IDs (for example emailId, sentId, forwarderId, inboxId),
  • timestamp and optional message details.

Recommended event workflow:

  1. After sending a test message, poll forwarder events by forwarderId.
  2. Verify at least one SUCCESS event for matched scenarios.
  3. For failures, inspect the event message and then verify recipient addresses and account sending permissions.
  4. For negative tests, confirm no SUCCESS events are created for non-matching messages.

Event checks are especially important when using nested matchOptions or attachment text extraction, where false positives are expensive.

Advanced examples

Here is an example of forwarding between inboxes.

typescript
const mailslurp = new MailSlurp({ apiKey: process.env.apiKey });
// create two inboxes for testing
const inbox1 = await mailslurp.inboxController.createInboxWithDefaults();
const inbox2 = await mailslurp.inboxController.createInboxWithDefaults();
const inbox3 = await mailslurp.inboxController.createInboxWithDefaults();
// add auto forwarding rule to inbox 2
await mailslurp.inboxForwarderController.createNewInboxForwarder({
// attach rule to inbox 2
inboxId: inbox2.id,
createInboxForwarderOptions: {
// filter emails that match the sender from inbox 1 and send to inbox 3
field: CreateInboxForwarderOptionsFieldEnum.SENDER,
match: inbox1.emailAddress,
forwardToRecipients: [inbox3.emailAddress],
},
});
// send email from inbox1 to inbox2
await mailslurp.sendEmail(inbox1.id!!, {
to: [inbox2.emailAddress!!],
subject: 'Hello',
});
// see that inbox2 gets the original email
const receivedEmail = await mailslurp.waitForLatestEmail(
inbox2.id,
60000,
true
);
expect(receivedEmail.subject).toContain('Hello');
// see that inbox3 gets a forwarded email
const forwardedEmail = await mailslurp.waitForLatestEmail(
inbox3.id,
60000,
true
);
expect(forwardedEmail.subject).toContain('Hello');
// check the sent messages for inbox2 to find the forwarded message
const sentFromInbox2 = await mailslurp.sentController.getSentEmails({
inboxId: inbox2.id,
});
expect(sentFromInbox2.totalElements).toEqual(1);
const forwardedMessage = sentFromInbox2.content[0];
expect(forwardedMessage.to).toEqual([inbox3.emailAddress]);
expect(forwardedMessage.from).toEqual(inbox2.emailAddress);

Other resources

You may also be interested in webhooks, rulesets, and auto-replies.