Inbound Email Overview
Receive and process incoming emails with Mail.Land's inbound gateway
MailLand's inbound gateway receives emails on your domains and delivers them to your application via webhooks or stores them for retrieval. Use inbound processing to build support inboxes, parse machine-generated emails, or route replies to your app.
How It Works
Configure your domain
Point your domain's MX records to MailLand's inbound servers. The domain needs inboundEnabled and an active tenant. MailLand accepts mail only for a recipient matching an enabled route, or any address if catchAllEnabled is on; anything else is refused at RCPT.
Create routing rules
Define routes that match the recipient's local part and assign an action (forward, webhook, or store).
Receive parsed emails
MailLand processes the email (parses headers, body, and attachments) and delivers the result to your webhook endpoint or stores it for API retrieval.
Inbound Settings
Settings are per domain, not workspace-wide:
GET https://api.mail.land/v1/transactional/inbound/domains/{domainId}/settings
PUT https://api.mail.land/v1/transactional/inbound/domains/{domainId}/settingsAuthenticate with Authorization: Bearer <token> or X-API-Key: <api key>.
curl https://api.mail.land/v1/transactional/inbound/domains/k3v9x2m8q1w7z4/settings \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."{
"isEnabled": true,
"catchAllEnabled": true,
"maxMessageSizeBytes": 26214400,
"retentionDays": 30,
"dailyLimit": 10000,
"storeEmails": true
}Update settings:
PUT https://api.mail.land/v1/transactional/inbound/domains/k3v9x2m8q1w7z4/settings{
"isEnabled": true,
"catchAllEnabled": true,
"maxMessageSizeBytes": 26214400,
"retentionDays": 30,
"dailyLimit": 10000,
"storeEmails": true
}curl -X PUT https://api.mail.land/v1/transactional/inbound/domains/k3v9x2m8q1w7z4/settings \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"isEnabled": true,
"catchAllEnabled": true,
"maxMessageSizeBytes": 26214400,
"retentionDays": 30,
"dailyLimit": 10000,
"storeEmails": true
}'| Field | Type | Description |
|---|---|---|
isEnabled | bool | Enable/disable inbound processing for this domain |
catchAllEnabled | bool | Accept mail to any recipient on the domain, not just recipients matching an enabled route |
maxMessageSizeBytes | int | Maximum accepted message size in bytes for this domain, enforced with SMTP 552 5.3.4. The settings API accepts up to 50 MiB, but the platform never accepts more than 25 MiB regardless of this setting |
dailyLimit | int | Maximum inbound messages accepted per day for this domain |
retentionDays | int | Days to retain stored emails. Default 30, allowed 1–90 |
storeEmails | bool | Whether to store the full parsed email for later retrieval |
routeCount | int | Number of routes configured for this domain |
There is no virus scanning, and there is no single workspace-wide webhookUrl/webhookSecret: webhooks are attached per route via webhookId (see Routes).
maxMessageSizeBytes, dailyLimit and retentionDays are enforced. Mail is received at <inboundSubdomain>.<domain>, the host the MX record points to, and the root domain still receives too. A message over the domain's maxMessageSizeBytes is refused with SMTP 552 5.3.4, and never above the platform limit of 25 MiB even if a larger value is set (the settings API accepts up to 50 MiB, but only 25 MiB is ever enforced). Once dailyLimit (default 10,000) is reached for the day, senders get a temporary 452 4.2.2 and are expected to retry later, mail is not lost. Stored messages are deleted once retentionDays (default 30, allowed 1–90) elapses.
Email Statuses
Returned as strings, not numbers:
| Status | Description |
|---|---|
Received | Email received, pending processing |
Processing | Being processed (parsing, spam check, routing) |
Processed | Processed and its action completed |
Delivered | Successfully delivered (forwarded or webhook sent) |
Failed | Processing failed |
Expired | Retention period elapsed; email was deleted |
There is no separate Quarantined status: spam is a boolean isSpam flag on the email, independent of status.
List Inbound Emails
GET https://api.mail.land/v1/transactional/inbound/emailscurl https://api.mail.land/v1/transactional/inbound/emails \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."| Parameter | Type | Default | Description |
|---|---|---|---|
cursor | string | - | Opaque cursor from the previous page's nextCursor. Omit for the first page. |
limit | int | 20 | Items per page (max 100), clamped rather than rejected |
domainId | string | - | Filter by domain public ID |
status | string | - | Filter by email status (Received, Processing, Processed, Delivered, Failed, Expired) |
routeId | string | - | Filter by matched route |
isSpam | bool | - | Filter by spam flag. Supplying isSpam (true or false) decides the spam dimension on its own and disables the default excludeQuarantined filter, so isSpam=true returns spam rows (still subject to the stored-only rule below). Omit isSpam to keep the inbox default of hiding spam. |
from | datetime | - | Emails received after this time |
to | datetime | - | Emails received before this time |
search | string | - | Search subject and sender (address or name); does not match the recipient |
sortBy | string | - | Sort field: subject, from, or the default receivedAt |
sortDesc | bool | true | Sort descending |
excludeQuarantined | bool | true | Exclude spam-flagged emails from the list. Ignored when isSpam is supplied. |
includeWebhookOnly | bool | false | Include emails that were not stored at receive time (webhook-only or drop routes). By default these are hidden, so a tenant whose routes are all webhook-only sees an empty list here. |
There is no subject-only search, no sender/recipient address filter by themselves, and from/to here are dates, not addresses (use search for a sender/subject text match).
{
"items": [
{
"id": "k7m2x9q4p1w8z3",
"domainId": "k3v9x2m8q1w7z4",
"domainName": "yourdomain.com",
"matchedRouteId": "r7k3m9q2p4w8x1",
"matchedRouteName": "Support inbox",
"fromAddress": "[email protected]",
"fromName": "Customer Name",
"subject": "Need help with my account",
"textPreview": "Hi, I need help with...",
"hasHtml": true,
"hasText": true,
"attachmentCount": 1,
"sizeBytes": 45230,
"status": "Delivered",
"spfResult": "Pass",
"dkimResult": "Pass",
"dmarcResult": "Pass",
"isSpam": false,
"spamScore": 1.2,
"receivedAt": "2026-02-17T14:30:00Z",
"expiresAt": "2026-03-19T14:30:00Z"
}
],
"nextCursor": "eyJ2IjoxLCJzIjoiY3JlYXRlZGF0OmRlc2MiLCJrIjoi...",
"hasMore": true
}There is no to field on the list item: recipients live on the detail response's envelopeRecipients/headerRecipients. status is a string, not a number.
Get Email Details
GET https://api.mail.land/v1/transactional/inbound/emails/{id}curl https://api.mail.land/v1/transactional/inbound/emails/{id} \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Returns the parsed email's metadata, headers, a text preview, and attachment metadata, not the full HTML/text body. Fetch the raw message for that (see below).
{
"id": "k7m2x9q4p1w8z3",
"domainId": "k3v9x2m8q1w7z4",
"domainName": "yourdomain.com",
"matchedRouteId": "r7k3m9q2p4w8x1",
"matchedRouteName": "Support inbox",
"fromAddress": "[email protected]",
"fromName": "Customer Name",
"subject": "Need help with my account",
"textPreview": "Hi, I need assistance with...",
"hasHtml": true,
"hasText": true,
"attachmentCount": 1,
"sizeBytes": 45230,
"status": "Delivered",
"spfResult": "Pass",
"dkimResult": "Pass",
"dmarcResult": "Pass",
"isSpam": false,
"spamScore": 1.2,
"receivedAt": "2026-02-17T14:30:00Z",
"expiresAt": "2026-03-19T14:30:00Z",
"envelopeFrom": "[email protected]",
"replyTo": "[email protected]",
"messageId": "<[email protected]>",
"inReplyTo": null,
"dateHeader": "2026-02-17T14:30:00Z",
"authResultsHeader": "spf=pass dkim=pass dmarc=pass",
"spamReason": null,
"sourceIp": "203.0.113.5",
"heloHostname": "mail.example.com",
"processingError": null,
"processedAt": "2026-02-17T14:30:01Z",
"envelopeRecipients": [
{ "address": "[email protected]", "localPart": "support", "matchedRouteId": "r7k3m9q2p4w8x1" }
],
"headerRecipients": [
{ "type": "To", "address": "[email protected]", "name": null }
],
"attachments": [
{ "id": "a3k7m2q9p4w1x8", "fileName": "screenshot.png", "contentType": "image/png", "sizeBytes": 34500, "isInline": false, "contentId": null }
],
"webhookAttempts": [
{ "attemptNumber": 1, "status": "Delivered", "webhookUrl": "https://yourapp.com/webhooks/inbound", "httpStatusCode": 200, "errorMessage": null, "durationMs": 145, "attemptedAt": "2026-02-17T14:30:02Z", "nextRetryAt": null }
]
}Authentication Results
Each inbound email includes SPF, DKIM, and DMARC verification results. The possible values differ per check:
headerRecipients[].type is "To", "Cc", or "Bcc" (matching case), and dateHeader is an ISO 8601 timestamp,
not the raw RFC 2822 header string.
spfResult | Description |
|---|---|
None | No SPF record found or check not performed |
Pass | Sender is authorized |
Fail | Sender is not authorized |
SoftFail | Sender may not be authorized |
Neutral | No statement about authorization |
TempError | Temporary DNS error |
PermError | Permanent DNS error |
dkimResult | Description |
|---|---|
None | No DKIM signature found or check not performed |
Pass | Signature verified |
Fail | Signature verification failed |
TempError | Temporary DNS error |
PermError | Permanent DNS error |
dmarcResult | Description |
|---|---|
None | No DMARC policy found or check not performed |
Pass | Alignment passed |
Fail | Alignment failed |
Quarantine | Policy recommends quarantine |
Reject | Policy recommends reject |
Download Raw Email
GET https://api.mail.land/v1/transactional/inbound/emails/{id}/rawcurl https://api.mail.land/v1/transactional/inbound/emails/{id}/raw \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-o email.emlReturns the original raw email (RFC 5322 format) as a file download.
Delete Emails
Delete a single email:
DELETE https://api.mail.land/v1/transactional/inbound/emails/{id}curl -X DELETE https://api.mail.land/v1/transactional/inbound/emails/{id} \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Bulk delete emails:
POST https://api.mail.land/v1/transactional/inbound/emails/bulk-delete{
"ids": ["k7m2x9q4p1w8z3", "p3q8w2x7m9k4z1", "w9k3p7m2q8x4z6"]
}curl -X POST https://api.mail.land/v1/transactional/inbound/emails/bulk-delete \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"ids": ["k7m2x9q4p1w8z3", "p3q8w2x7m9k4z1", "w9k3p7m2q8x4z6"]
}'There is also POST /v1/transactional/inbound/emails/bulk-mark-spam with the same {"ids": [...]} body, to flag up to the same batch of emails as spam in one call.
Quarantine
Emails with isSpam: true show up here (there is no virus scanning, and no separate Quarantined status, spam is a boolean flag). View quarantined emails:
GET https://api.mail.land/v1/transactional/inbound/quarantinecurl https://api.mail.land/v1/transactional/inbound/quarantine \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Release an email from quarantine:
POST https://api.mail.land/v1/transactional/inbound/quarantine/{id}/releasecurl -X POST https://api.mail.land/v1/transactional/inbound/quarantine/{id}/release \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Releasing an email only clears its isSpam flag. It does not re-run routing and does not re-send webhooks. To re-deliver the webhook for a released (or any) email, call POST /v1/transactional/inbound/emails/{id}/replay-webhook: this queues one durable delivery to the route's matched webhook and returns 202 Accepted (see Inbound Webhooks). An optional ?addToAllowlist=true query parameter also adds the sender to the allowlist at release time.
Allowlist and Blocklist
Allowlist
Emails from allowlisted senders bypass spam filtering:
GET https://api.mail.land/v1/transactional/inbound/allowlistcurl https://api.mail.land/v1/transactional/inbound/allowlist \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."POST https://api.mail.land/v1/transactional/inbound/allowlist{
"pattern": "[email protected]",
"matchType": "ExactEmail",
"description": "Trusted partner"
}curl -X POST https://api.mail.land/v1/transactional/inbound/allowlist \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"pattern": "[email protected]",
"matchType": "ExactEmail",
"description": "Trusted partner"
}'Returned and sent as strings, not numbers:
| Match Type | Description |
|---|---|
ExactEmail | Match exact email address |
Domain | Match all emails from domain |
WildcardPattern | Wildcard pattern match |
Blocklist
Emails from blocklisted senders are automatically handled:
GET https://api.mail.land/v1/transactional/inbound/blocklistcurl https://api.mail.land/v1/transactional/inbound/blocklist \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."POST https://api.mail.land/v1/transactional/inbound/blocklist{
"pattern": "[email protected]",
"matchType": "ExactEmail",
"action": "RejectAtSmtp",
"description": "Known spammer"
}curl -X POST https://api.mail.land/v1/transactional/inbound/blocklist \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"pattern": "[email protected]",
"matchType": "ExactEmail",
"action": "RejectAtSmtp",
"description": "Known spammer"
}'Returned and sent as strings, not numbers. Default action is MarkAsSpam if omitted:
| Blocklist Action | Description |
|---|---|
RejectAtSmtp | Reject at SMTP level (550 response) |
MarkAsSpam | Accept but mark as spam (default) |
Drop | Silently drop the email |
Delete from allowlist or blocklist:
DELETE https://api.mail.land/v1/transactional/inbound/allowlist/{id}
DELETE https://api.mail.land/v1/transactional/inbound/blocklist/{id}# Delete from allowlist
curl -X DELETE https://api.mail.land/v1/transactional/inbound/allowlist/{id} \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
# Delete from blocklist
curl -X DELETE https://api.mail.land/v1/transactional/inbound/blocklist/{id} \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Inbound Statistics
GET https://api.mail.land/v1/transactional/inbound/statscurl https://api.mail.land/v1/transactional/inbound/stats \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."| Parameter | Type | Description |
|---|---|---|
domainId | string | Filter by domain public ID |
from | datetime | Filter by receivedAt on or after this time |
to | datetime | Filter by receivedAt before the end of this day |
{
"totalEmails": 1500,
"todayEmails": 42,
"receivedCount": 1500,
"processingCount": 5,
"processedCount": 1345,
"failedCount": 20,
"spamCount": 80,
"webhookFailedCount": 3,
"storageUsedBytes": 52428800,
"byDomain": [
{ "domainId": "k3v9x2m8q1w7z4", "domainName": "yourdomain.com", "count": 1500 }
],
"byStatus": { "Received": 5, "Processing": 5, "Processed": 1345, "Delivered": 1300, "Failed": 20, "Expired": 50 },
"dailyStats": [
{ "date": "2026-02-17", "total": 120, "processed": 115, "spam": 8 }
]
}There is no spamRate/averageSizeBytes field, the shape above is exhaustive.
Error Responses
| Status | Code | Description |
|---|---|---|
400 | VALIDATION_FAILED | Invalid request parameters |
403 | FORBIDDEN | Insufficient permissions |
404 | RESOURCE_NOT_FOUND | Email not found |