Receiving Mail

Primitive receives inbound mail through MX, parses it, stores it, and delivers it to your code as a signed JSON event.

You can receive through a webhook endpoint you host, a Primitive Function hosted by Primitive, or the REST API after the message is stored.

Inbound Flow

external sender
      |
      v
Primitive MX
      |
      v
parse + authenticate + store
      |
      +--> signed webhook endpoint
      +--> hosted Primitive Function
      +--> REST API resource

Every inbound event has a stable event id. Retries reuse that id, so your handler can be idempotent.

Webhook Endpoint

Set a webhook URL in the dashboard or via the API. The endpoint must be HTTPS and publicly reachable.

Primitive sends application/json with a Primitive-Signature header. Verify the signature before trusting the payload.

import primitive from '@primitivedotdev/sdk';


export async function POST(req: Request) {
  const email = await primitive.receive(req, {
    secret: process.env.PRIMITIVE_WEBHOOK_SECRET!,
  });


  console.log(email.sender.address, email.subject, email.text);
  return Response.json({ ok: true });
}

Return any 2xx response to acknowledge delivery. Non-2xx responses and network failures are retried.

Primitive Functions

Functions are hosted receive handlers. Deploy a JavaScript handler, and Primitive creates the inbound endpoint for you. Use Functions when you do not want to run a public webhook server.

See Functions for the handler shape, deploy flow, logs, and secrets.

Signature Verification

Every webhook is signed. You must verify the exact raw request body, not a re-serialized JSON object.

The high-level primitive.receive(...) helper verifies signatures automatically. Lower-level helpers are documented in Signature Verification.

Retries

Primitive retries webhook delivery when your endpoint does not return a 2xx response. The delivery.attempt field starts at 1 and increments on each retry.

Track the event id or email id in your own system. If you already processed the event, return 2xx again instead of failing the duplicate.

Retry-safe handlers should:

  • use event.id or email.id as a dedupe key;
  • perform external side effects idempotently;
  • return 2xx for duplicate events that were already processed;
  • avoid returning 4xx for unknown future event types you intentionally ignore.

Primitive retries with backoff for delivery failures. A handler that keeps failing will surface in webhook delivery logs and can be replayed after you deploy a fix.

Replays

Replay an inbound email when you need to re-deliver the stored event after fixing your endpoint.

primitive emails:replay-email-webhooks --id <email-id>

Webhook delivery attempts can also be replayed from the webhook delivery API.

Replay does not create a new inbound email. It reuses the stored event and delivery data, so the same idempotency rules apply.

Endpoint Rules

Endpoints can be scoped to domains and rules. Use this when one organization has multiple inbound workflows and only some addresses should reach a given endpoint.

Rules are evaluated before delivery. If no active endpoint matches an inbound email, the message is still stored and can be read from the REST API.

Domain-scoped endpoints override fallback endpoints. For an accepted inbound on a verified domain, Primitive first looks for enabled endpoints pinned to that domain. If any exist, only those endpoints are considered. Endpoints with domain_id = null receive mail only for domains without their own enabled scoped endpoint.

Use endpoint rules when one organization has several mail workflows, for example:

  • support@yourdomain.com posts to a helpdesk webhook;
  • bugs@yourdomain.com invokes a triage Function;
  • receipts@yourdomain.com is stored for polling only.

Rules should be narrow enough that a message reaches the intended handler, but not so narrow that important mail is silently skipped. Two endpoints with the same scope both fire on every matching inbound, so keep one enabled endpoint for a domain when you want a single Function or webhook to own it. Even skipped mail remains available through the REST API.

Stored Email Access

List and fetch inbound mail with the API:

curl https://api.primitive.dev/v1/emails \
  -H "Authorization: Bearer $PRIMITIVE_API_KEY"


curl https://api.primitive.dev/v1/emails/<id> \
  -H "Authorization: Bearer $PRIMITIVE_API_KEY"

The full email resource includes parsed headers, body fields, attachments metadata, authentication results, delivery data, and download URLs where applicable.

Use search for operational workflows that need to filter stored mail by sender, recipient, subject, body text, date, or processing state.

Raw Content and Attachments

Small raw emails may be included inline in the webhook payload. Larger raw content is stored and exposed through signed download URLs. Attachment downloads use signed URLs rather than API keys.

Related Pages