MailLandDocs

Inbound Routes

Configure routing rules to direct incoming emails to storage, webhooks, or other actions

Routes define how MailLand handles incoming emails for a domain. Each route matches on the recipient's local part (the part of the address before @) and runs one or more actions: store, deliver to a webhook, forward, reject at SMTP, or drop. Routes are evaluated in priority order (lowest number first) and the first match wins.

Routes match the local part of the recipient address only. There is no sender, subject, or header matching: a route cannot filter on from or subject.

Match Types

Match TypeDescription
exactLocal part equals the pattern exactly
prefixLocal part starts with the pattern
suffixLocal part ends with the pattern
containsLocal part contains the pattern
plusaddressingLocal part is the pattern followed by +anything (support matches support+ticket123)
catchallMatches every recipient; no pattern needed
regexLocal part matches the pattern as a regular expression (max 200 characters, 1 second timeout)

Matching is case-insensitive and always runs against the local part, [email protected] and [email protected] both match a route with pattern support scoped to the same domain.

The regex match type requires a plan with regex inbound routing (Pro and Max today, not Free). Creating a route with matchType: "regex", or updating an existing route so its match type becomes regex, on a plan without it returns 400 PLAN_LIMIT_REACHED. An existing regex route keeps matching even if the workspace later downgrades, and editing it without changing its match type is unaffected.

Route Actions

A route's actions field is a list of one or more of the following string values:

ActionDescription
storeStore the parsed email for retrieval via the API and dashboard
webhookDeliver the parsed email to a webhook (requires webhookId)
forwardSend a copy of the email to forwardToEmail, through the normal sending path
rejectRefuse the message at the SMTP level with rejectStatus/rejectMessage
dropAccept the message at SMTP, then silently discard it, no storage, no webhook
unsubscribeTreat the address as a List-Unsubscribe mailbox; terminal, see below

store and webhook can be combined with each other and with forward. reject, drop, and unsubscribe are terminal: each must be the only action on the route; the API rejects a request that combines one of them with any other action.

A forward copy is not sent by raw SMTP relay: it goes through the same sending pipeline as any other transactional email, which is why it needs the destination's consent (below), counts against your sending quota, and appears in your message log like any other send.

Forward Destinations

Before any mail is forwarded to an address, that address's owner must confirm they want it: unless the address is on one of your own domains that is fully verified for sending. There is no way around this for an outside address: it protects the destination's owner from receiving mail they never agreed to, and it protects your sending reputation from an address that bounces or complains.

When you create or update a route with forward in actions, MailLand automatically:

  1. Creates (or reuses) a forward-destination record for forwardToEmail, scoped to your tenant: the same address is remembered across every route that forwards to it.
  2. If the address needs confirmation and has never been asked, sends it a confirmation email containing a link.

The route save itself always succeeds; a failed confirmation email does not fail the route: the destination simply stays pending (nothing is forwarded to it) until you resend the confirmation.

The link in the confirmation email is valid for 7 days and is single-use: once the recipient confirms or declines, visiting the link again just shows the same decision, it cannot be reversed by revisiting it. An expired, never-decided link returns 410 Gone if opened again.

  • Confirm: mail forwarded to this tenant will now be sent to the address.
  • Decline: final. Nothing is ever forwarded to the address for this tenant again, and the tenant cannot request confirmation again for it.

Get a Forward Destination

GET https://api.mail.land/v1/transactional/inbound/forward-destinations/{destinationId}
Forward destination response
{
  "id": "f2k9m3q7p4w8x1",
  "email": "[email protected]",
  "status": "pending",
  "confirmedVia": null,
  "lastSentAt": "2026-09-26T10:00:00Z",
  "confirmedAt": null,
  "declinedAt": null
}

status is one of pending, confirmed or declined. confirmedVia is link when the owner confirmed through the email, verified_domain when the address is on one of your own sending-verified domains and needed no confirmation, or null otherwise. A route's own response also carries this same object under forwardDestination (see the route response below).

Resend a Confirmation

POST https://api.mail.land/v1/transactional/inbound/forward-destinations/{destinationId}/resend

Only for a destination that is still pending and does need a link (an address on one of your own verified sending domains returns 409 FORWARD_DESTINATION_NO_CONFIRMATION_NEEDED, since there is nothing to resend). Rate-limited: at most once every 5 minutes and 5 times a day per destination, and 50 times a day across the whole tenant. Exceeding either limit returns 429 FORWARD_CONFIRMATION_RATE_LIMITED.

There is no way to un-decline a destination through the API once it has declined: a declined address stays refused for that tenant.

Create a Route

Routes belong to a domain and are created under that domain.

POST https://api.mail.land/v1/transactional/inbound/domains/{domainId}/routes
Create route request
{
  "name": "Support Inbox",
  "matchType": "exact",
  "matchPattern": "support",
  "actions": ["store", "webhook"],
  "webhookId": "w4k8m2q7p9x3z1",
  "priority": 10
}
FieldTypeRequiredDescription
namestringYesRoute name
matchTypestringYesOne of the match types above
matchPatternstringYesPattern to match against the local part (ignored for catchall, max 64 characters, max 200 for regex)
actionsstring[]YesOne or more of store, webhook, forward, reject, drop, unsubscribe
webhookIdstringCond.Public ID of an existing webhook; required when actions includes webhook. Routes never create a webhook inline: create it first on the Webhooks page
forwardToEmailstringCond.Destination for forward; required when actions includes forward. Must be a bare address (no display name, no angle brackets). The route's own domain must be fully verified for sending, the forwarded copy is sent from it, or the request is rejected with 400 FORWARD_DOMAIN_NOT_VERIFIED. The destination itself must separately confirm, unless it is on one of your own sending-verified domains (see Forward Destinations)
rejectStatusstringNoSMTP status for reject, e.g. 550 or 550 5.1.1 (default 550 5.1.1)
rejectMessagestringNoSMTP rejection text for reject
priorityintNoLower number = evaluated first. Defaults to one more than the domain's current highest priority. Must be unique per domain
Create an inbound route
curl -X POST https://api.mail.land/v1/transactional/inbound/domains/k3v9x2m8q1w7z4/routes \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Support Inbox",
    "matchType": "exact",
    "matchPattern": "support",
    "actions": ["store", "webhook"],
    "webhookId": "w4k8m2q7p9x3z1",
    "priority": 10
  }'

Response

Route response
{
  "id": "r7k3m9q2p4w8x1",
  "name": "Support Inbox",
  "domainId": "k3v9x2m8q1w7z4",
  "domainName": "yourdomain.com",
  "priority": 10,
  "isEnabled": true,
  "matchType": "Exact",
  "matchPattern": "support",
  "actions": ["store", "webhook"],
  "webhookId": "w4k8m2q7p9x3z1",
  "webhookName": "Support Webhook",
  "webhookUrl": "https://yourapp.com/webhooks/support",
  "forwardToEmail": null,
  "forwardDestination": null,
  "rejectStatus": null,
  "rejectMessage": null,
  "createdAt": "2026-02-17T12:00:00Z",
  "updatedAt": "2026-02-17T12:00:00Z"
}

Requests accept matchType in any case; responses always return the enum name capitalized: Exact, Prefix, Suffix, Contains, PlusAddressing, CatchAll, Regex. Do not compare a response's matchType against a lowercase string.

forwardDestination is only populated when actions includes forward; it carries the same object described in Get a Forward Destination:

Route response with a pending forward
{
  "...": "...",
  "actions": ["store", "forward"],
  "forwardToEmail": "[email protected]",
  "forwardDestination": {
    "id": "f2k9m3q7p4w8x1",
    "email": "[email protected]",
    "status": "pending",
    "confirmedVia": null,
    "lastSentAt": "2026-09-26T10:00:00Z",
    "confirmedAt": null,
    "declinedAt": null
  }
}

List Routes for a Domain

GET https://api.mail.land/v1/transactional/inbound/domains/{domainId}/routes

Returns every route for the domain, sorted by priority (lowest first).

List routes for a domain
curl https://api.mail.land/v1/transactional/inbound/domains/k3v9x2m8q1w7z4/routes \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Route list response
{
  "items": [
    { "id": "r7k3m9q2p4w8x1", "name": "Support Inbox", "priority": 10, "isEnabled": true, "..." : "..." }
  ],
  "totalCount": 1
}

List All Routes

To list routes across every domain in the tenant, use the cursor-paginated endpoint instead:

GET https://api.mail.land/v1/transactional/inbound/routes
ParameterTypeDefaultDescription
cursorstring-Opaque cursor from the previous page's nextCursor. Omit for the first page
limitint50Items per page

Get Route Details

GET https://api.mail.land/v1/transactional/inbound/routes/{routeId}
Get route details
curl https://api.mail.land/v1/transactional/inbound/routes/r7k3m9q2p4w8x1 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Update a Route

PUT https://api.mail.land/v1/transactional/inbound/routes/{routeId}

The request body matches the create request, but every field is optional: only the fields you send are updated.

Update an inbound route
curl -X PUT https://api.mail.land/v1/transactional/inbound/routes/r7k3m9q2p4w8x1 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "matchPattern": "help",
    "priority": 5
  }'

Toggle a Route

Flips isEnabled without touching any other field. No request body.

POST https://api.mail.land/v1/transactional/inbound/routes/{routeId}/toggle
Toggle a route
curl -X POST https://api.mail.land/v1/transactional/inbound/routes/r7k3m9q2p4w8x1/toggle \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Reorder Routes

Update the priority of every route in a domain at once, by passing the route IDs in the desired order: the first ID becomes priority 1, the second priority 2, and so on.

POST https://api.mail.land/v1/transactional/inbound/domains/{domainId}/routes/reorder
Reorder routes request
{
  "routeIds": ["r7k3m9q2p4w8x1", "m2p8w4x1k7q3z9", "x4k1p7m3q9w2z8"]
}
Reorder routes
curl -X POST https://api.mail.land/v1/transactional/inbound/domains/k3v9x2m8q1w7z4/routes/reorder \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "routeIds": ["r7k3m9q2p4w8x1", "m2p8w4x1k7q3z9", "x4k1p7m3q9w2z8"]
  }'

Test a Route

Check which route would match a given recipient address without processing an email.

POST https://api.mail.land/v1/transactional/inbound/domains/{domainId}/routes/test
Test route request
{
  "emailAddress": "[email protected]"
}
Test a route
curl -X POST https://api.mail.land/v1/transactional/inbound/domains/k3v9x2m8q1w7z4/routes/test \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "emailAddress": "[email protected]"
  }'
Test route response: match found
{
  "matched": true,
  "matchedRouteId": "r7k3m9q2p4w8x1",
  "matchedRouteName": "Support Inbox",
  "actions": ["store", "webhook"]
}

If no route matches, the response is 200 with matched: false and matchedRouteId/ matchedRouteName/actions all null. 404 means the domain was not found, or the tested address is invalid (no @).

Delete a Route

DELETE https://api.mail.land/v1/transactional/inbound/routes/{routeId}
Delete a route
curl -X DELETE https://api.mail.land/v1/transactional/inbound/routes/r7k3m9q2p4w8x1 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Route Examples

Catch-all route: store every unmatched recipient:

Catch-all route
{
  "name": "Catch All",
  "matchType": "catchall",
  "matchPattern": "*",
  "actions": ["store"],
  "priority": 999
}

Plus-addressing: route all support+*@yourdomain.com variants to one webhook:

Plus-addressing route
{
  "name": "Support Tickets",
  "matchType": "plusaddressing",
  "matchPattern": "support",
  "actions": ["store", "webhook"],
  "webhookId": "w4k8m2q7p9x3z1",
  "priority": 5
}

Reject at SMTP: refuse mail to a retired mailbox before it is accepted:

Reject route
{
  "name": "Retired Mailbox",
  "matchType": "exact",
  "matchPattern": "old-team",
  "actions": ["reject"],
  "rejectStatus": "550 5.1.1",
  "rejectMessage": "Mailbox does not exist",
  "priority": 1
}

Routes are evaluated in priority order (lowest number first). Place specific routes at lower priority numbers and catch-all routes at the highest number so they run last.

What a Forwarded Copy Looks Like

A forwarded copy is a new, ordinary transactional email, not a raw relay of the original bytes, sent from your verified domain so it can pass DKIM and DMARC at the destination's mailbox provider:

  • From: "<original sender name> via <matched address>@<your domain>", e.g. "Jane Doe via [email protected]". A display name that looks like an address (contains @, < or >) is replaced with the bare original address, so a copy can never impersonate a brand under your DKIM.
  • Reply-To: the original message's Reply-To if it had one, otherwise the original sender's address, so replies reach the real author, not you.
  • No tracking: open and click tracking are always off on a forwarded copy, whatever your workspace's tracking settings are.
  • No branding footer: the Free-plan footer that other sends may carry is never added.
  • Attachments: filtered through the same rules as an outbound API send (type, count, total size). Anything left out is noted at the top of the copy: "Forwarded without N attachment(s) that exceed the outbound attachment limits: ...", the copy still arrives, just without those parts.
  • Tag: every forwarded copy carries the tag inbound-forward, so you can find them in your regular message log; its delivery status (queued, sent, delivered, bounced, etc.) is the same status any other transactional send gets, because it goes through the same delivery pipeline.
  • Usage: each forwarded copy counts as one send against your plan's quota and billing, shown as its own "Inbound forward" line in usage, separate from API, SMTP, campaign and automation sends.

What Is Never Forwarded

A message matching a forward route is not forwarded, and never billed, when:

  • It was flagged as spam, or the sender is blocklisted as spam.
  • It failed DMARC under the sender domain's effective policy of quarantine or reject, resolved by walking up from the author domain to find the nearest applicable record, so a subdomain's own sp= overrides its organizational domain's p=, and a t=y (testing) or pct=0 record is treated one policy level lower, forwarding it would re-sign a message the sender explicitly asked receivers to refuse.
  • It, or its HTML/text body, is over the platform's outbound size limits.
  • Forwarding it would create a loop: the destination is one of the message's own recipients, or it already carries too many hops or Received headers.

A copy that is eligible but temporarily can't be sent (a policy delay, a quota window, an outage) is retried with back-off for up to 24 hours, then expires without being sent. A route that is disabled, deleted, or has its destination changed after a message was received also stops any copy still waiting for it: the current route configuration is always authoritative.

Checking a Forward's Outcome

Every inbound email that matched a forward route carries a forwards array on its detail response (GET /v1/transactional/inbound/emails/{id}): one entry per destination the message was queued to forward to, so you can see what actually happened, not just where it was configured to go:

forwards on an inbound email
"forwards": [
  {
    "destination": "[email protected]",
    "status": "Admitted",
    "reason": null,
    "omittedAttachmentCount": 0,
    "messageId": "m_01hz3k...",
    "createdAt": "2026-09-26T10:00:00Z",
    "updatedAt": "2026-09-26T10:00:02Z"
  }
]
FieldTypeDescription
destinationstringThe forward destination address
statusstringOne of the statuses below
reasonstring | nullMachine-readable detail code (for example hop_limit), or null
omittedAttachmentCountnumberAttachments left out of the copy for exceeding outbound attachment limits
messageIdstring | nullThe forwarded copy's message id once admitted, otherwise null
createdAt / updatedAtstringISO-8601 timestamps for the forward record

status is one of:

StatusMeaning
PendingWritten with the inbound message; not yet claimed by the forward worker
ProcessingClaimed by the forward worker; in flight
DeferredA transient refusal (policy delay, rate, quota window, outage); retried with back-off for up to 24 hours
AdmittedSent through the normal sending pipeline: messageId names the copy, whose own delivery status (queued/sent/delivered/bounced) is authoritative
SkippedLoopLoop protection: hop limit, too many Received headers, or the destination is one of the message's own recipients
SkippedSpamThe message was flagged as spam, or the sender is blocklisted as spam
SkippedAuthFailDMARC failed under the sender domain's effective policy of quarantine or reject (subdomain sp= honored)
SkippedTooLargeThe message is over the outbound forward size cap
RefusedSuppressedThe destination is suppressed, or refused by recipient protection
RefusedDomainNotVerifiedThe route's domain is not (or no longer) verified for sending
RefusedPolicyA terminal policy or account refusal
RefusedRouteInactiveThe route was disabled, deleted, lost its forward action, or changed destination after the message was received
RefusedInvalidThe stored message could not be turned into a sendable copy
ExpiredStill Deferred when the 24-hour retry window closed
RefusedDestinationUnconfirmedThe destination has not confirmed (see Forward Destinations)
RefusedDestinationDeclinedThe destination's owner declined; nothing is ever forwarded to it

Pending, Processing and Deferred are the only non-final statuses, every other value is terminal.

Forward Error Responses

StatusCodeDescription
400VALIDATION_FAILED (field forwardToEmail)On create: actions includes forward but forwardToEmail is empty
400FORWARD_DESTINATION_REQUIREDOn update (PUT): the resulting effective route forwards but has no forwardToEmail
400FORWARD_DOMAIN_NOT_VERIFIEDThe route's domain is not fully verified for sending, so no forwarded copy could ever be sent from it
404RESOURCE_NOT_FOUNDForward destination not found
409FORWARD_DESTINATION_NOT_PENDINGResend requested for a destination that already confirmed or declined
409FORWARD_DESTINATION_NO_CONFIRMATION_NEEDEDResend requested for a destination on one of your own verified sending domains
410FORWARD_CONFIRMATION_EXPIREDThe confirmation link expired (7 days) while still undecided
409FORWARD_CONFIRMATION_ALREADY_DECIDEDThe link was used for the other decision
429FORWARD_CONFIRMATION_RATE_LIMITEDResend requested too soon, or the daily resend limit was reached

Error Responses

StatusCodeDescription
400VALIDATION_FAILEDMissing required fields, invalid matchType/actions, or a terminal action combined with another
400INVALID_REQUESTA route with this priority already exists for the domain, or the referenced webhookId was not found
400PLAN_LIMIT_REACHEDCreating a route with matchType: "regex", or updating a route so its match type becomes regex, on a plan without regex route support (Free today)
403FORBIDDENInsufficient permissions
404RESOURCE_NOT_FOUNDDomain or route not found (test endpoint: also an invalid address without @; no match is 200 with matched: false, not a 404)

Next Steps

On this page