API Reference
API Reference
GitHub ↗
Inbound Email

Managing Inbound Email

List, fetch, retry, export, and stream inbound email from your project

Mail that arrived on the MX listener is stored per project and read through these routes. They are ordinary /api/v1 endpoints under the inbound permission — an API key carries its project, a session needs the X-Mailyard-Project-Id header.

Listing

GET /api/v1/inbound-emails?limit=50&status=received
ParamNotes
statusOne of the three below, without regard to case. Anything else is a 400
limitDefault 50, maximum 200
cursorThe next_cursor of the previous page
senderPart of the From header, without regard to case
recipientPart of any envelope recipient
matchcontains (the default) or exact. Exact makes sender and recipient one whole address each, the sender found bare or inside Name <address>
searchPart of the subject, without regard to case

There are three statuses, and no others:

StatusMeans
receivedStored and parsed
rejectedRefused at ingest — a suppressed sender, or a DMARC failure on a p=reject domain
failedThe MIME tree could not be parsed. The raw bytes are still there

Paging is a keyset cursor, like the outbound log, and for the same reason: this list grows on its own. Follow next_cursor until it comes back empty. The cursor encodes received_at and the id together, so two messages sharing a received_at across a page boundary are neither repeated nor skipped. There is no total and no offset.

curl -G http://localhost:3000/api/v1/inbound-emails \
  -H "Authorization: Bearer myk_..." \
  --data-urlencode "limit=50" \
  --data-urlencode "status=received"

Counts

GET /api/v1/inbound-emails/stats?from=2026-08-01&to=2026-08-31

from and to are optional and bound received_at the way the list’s cursor does.

{
    "counts": {
        "received": 812,
        "rejected": 40,
        "failed": 3
    }
}

Get an Inbound Email

GET /api/v1/inbound-emails/{id}

{id} is the inbound email UUID. Returns the full record:

{
    "inbound_email": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "project_id": "81af718e-f0ae-4780-a0d7-9f05b34dabcc",
        "domain_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
        "domain": "yourdomain.com",
        "message_id": "abc123@example.com",
        "sender": "Jane Doe <jane@example.com>",
        "bounce_address": "bounces@example.com",
        "recipients": [
            "inbox@yourdomain.com"
        ],
        "subject": "Hello",
        "text_body": "Plain text body",
        "html_body": "<p>HTML body</p>",
        "headers": {
            "Received": "from mail.example.com ..."
        },
        "attachments": [
            {
                "filename": "invoice.pdf",
                "content_type": "application/pdf",
                "size": 18244
            }
        ],
        "auth": {
            "spf": "pass",
            "dkim": "pass",
            "dmarc": "pass",
            "aligned": true,
            "client_ip": "203.0.113.10"
        },
        "has_raw": true,
        "size": 2048,
        "status": "received",
        "received_at": "2026-01-01T00:00:00Z",
        "created_at": "2026-01-01T00:00:00Z"
    }
}

sender is the From header, who the message says it is from, and it is empty when the message could not be parsed. bounce_address is the SMTP MAIL FROM - where bounces for the message go and what SPF is checked against - which is often not the sender. The stored headers carry it as Return-Path, written at receipt. domain is the name of the domain the mail arrived for. The sender search parameter on the list matches sender only, and recipient the envelope recipients.

auth carries the SPF, DKIM and DMARC verdicts stamped at ingest. aligned is the field worth acting on - a valid signature from some other domain is not authentication. See Receiving .

Attachment entries carry metadata only. Fetch the bytes through the download endpoint below.

Delete an Inbound Email

DELETE /api/v1/inbound-emails/{id}

Removes the record and best-effort deletes its blob-stored attachments. Returns 204 No Content.

Re-dispatch

POST /api/v1/inbound-emails/{id}/retry
{
    "emitted": true
}

This re-sends the webhook, it does not re-process the mail

The name says retry, and what it actually does is emit the inbound.received webhook event again for a message already stored. The message is not re-parsed and its status does not change.

It works on any stored message regardless of status, and it is not conditional on anything having failed. So it is the tool for “my receiver was down when that arrived” — replaying the notification — and not for recovering a failed parse, which nothing re-runs.

emitted: true means the event was handed to the dispatcher, not that your endpoint answered. Check delivery tracking for that.

Download the Message as .eml

GET /api/v1/inbound-emails/{id}/eml

Answers the message as message/rfc822 with a .eml filename. For a message that failed to parse this is the original wire bytes, kept for exactly this. For every other message it is rebuilt from the stored headers, bodies and attachments: embedded images keep their Content-ID, signatures computed over the original bytes (DKIM-Signature, ARC-*) are left out, and the multipart structure is ours rather than the sender’s. Returns 404 when nothing of the content is stored, as after the content retention sweep.

Download an Attachment

GET /api/v1/inbound-emails/{uuid}/attachments/{idx}

Streams an attachment by zero-based index for an inbound email you own. The response uses the attachment’s original Content-Type and filename. Where the bytes are actually stored depends on MAILYARD_STORAGE_BACKEND, but this endpoint reads them the same way either way.

Live updates

Each arrival is published on the console’s live event stream as email.inbound.received. That stream reaches only the viewers connected to the node that received the message. To be notified machine-side, subscribe to inbound.received with a webhook . It carries metadata only - no bodies or attachments - so fetch those with the endpoints above. See Event Types for the payload.