Custom Routes

A route binds a recipient address pattern to one destination endpoint. Inbound mail resolves to a single destination at delivery time — no fan-out — and the decision is fully explainable: you can ask where any address would land, and why, before a single email arrives.

This is the product stance that an email address is a dispatch pattern, not a mailbox. A route maps where mail goes; your handler still owns what happens next.

All endpoints below live under https://api.primitive.dev/v1. Every command shown also exists as a primitive routes CLI verb.

Requesting access

Custom routing is rolling out gradually and is gated by the recipient_routing organization entitlement, off by default. If the /v1/routes endpoints return feature_disabled, the feature is not enabled for your organization yet. To request access, contact support or email the Primitive dev_help agent at dev_help@agent.primitive.dev.

Concepts

  • Pattern — the recipient address a route matches on, in one of three tiers:
    • exact — one full address, e.g. billing@acme.dev.
    • wildcard — a glob over the local part, e.g. *@acme.dev (the rest of a domain).
    • regex — a safe, linear-time pattern. Regex matching is not generally available yet; on current plans, use exact or wildcard.
  • Destination — exactly one endpoint. Provide an existing endpoint_id, or pass a function_id: Primitive binds the function's existing route-target endpoint, or mints one in the same transaction if the function has none (per-address function routing).
  • Priority — evaluation order within a scope. Lower is checked first.
  • Scope — a route is scoped to a domain, or org-wide when domain_id is null. Domain-scoped routes are evaluated before org-wide ones.
  • Fallback — if no route matches, mail falls through to the domain's default endpoint, then the org default. If neither exists, the outcome is none: the mail is stored, and nothing is delivered.

How a route is chosen

For a given normalized recipient, enabled matching routes are ranked in a deterministic order and the first match wins:

  1. Scope — domain-scoped routes outrank org-wide routes. This is the primary key, so a domain-scoped route at priority: 999 still beats an org-wide route at priority: 1.
  2. Priority ascending — within the same scope, lower priority is checked first.
  3. Tier specificityexact > wildcard > regex.
  4. Wildcard literal length — a wildcard with more fixed (non-*) characters is more specific and wins.
  5. Creation time, then route id, as stable final tiebreaks.

Mail is delivered to that one endpoint, and the full evaluation trace is recorded, so every delivery can answer "what went where, and why."

Create a route

Bind an exact address to a function. With function_id, the route-target endpoint is minted and bound for you:

curl -X POST https://api.primitive.dev/v1/routes \
  -H "Authorization: Bearer $PRIMITIVE_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "match_type": "exact",
    "pattern": "billing@acme.dev",
    "function_id": "<function-uuid>"
  }'

Catch the rest of the domain with a wildcard pointed at an existing endpoint, at a lower priority so the exact rule always wins:

curl -X POST https://api.primitive.dev/v1/routes \
  -H "Authorization: Bearer $PRIMITIVE_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "match_type": "wildcard",
    "pattern": "*@acme.dev",
    "endpoint_id": "<endpoint-uuid>",
    "priority": 200
  }'

Provide exactly one of endpoint_id or function_id. priority defaults to 100 and enabled defaults to true.

Simulate before you ship

Ask where an address would land — and why, rule by rule — without sending anything:

curl -X POST https://api.primitive.dev/v1/routes/simulate \
  -H "Authorization: Bearer $PRIMITIVE_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "recipient": "sales@acme.dev" }'
{
  "outcome": "matched",
  "recipient": "sales@acme.dev",
  "endpoint_id": "a0d6f29c-4e13-4b87-9c52-1f8a3e7b6d09",
  "matched_route_id": "c4a9e1b7-3d28-4f60-b915-8e2c7a0d6f31",
  "matched_tier": "wildcard",
  "matched_pattern": "*@acme.dev",
  "default_scope": null,
  "evaluated": [
    {
      "route_id": "b7e2d4a1-9c3f-4e08-8a17-2d6c5f0b9e44",
      "tier": "exact",
      "pattern": "billing@acme.dev",
      "result": "miss",
      "reason": "recipient 'sales@acme.dev' is not 'billing@acme.dev'"
    },
    {
      "route_id": "c4a9e1b7-3d28-4f60-b915-8e2c7a0d6f31",
      "tier": "wildcard",
      "pattern": "*@acme.dev",
      "result": "hit"
    }
  ],
  "truncated": false
}

outcome is matched (a route hit), defaulted (no route matched, a fallback endpoint applied — see default_scope), or none (nothing matched and no fallback exists). event_type defaults to email.received; pass it to model how a different event subscribes.

List, reorder, update, delete

# routes in evaluation order
curl https://api.primitive.dev/v1/routes \
  -H "Authorization: Bearer $PRIMITIVE_AUTH_TOKEN"


# repriority in bulk
curl -X POST https://api.primitive.dev/v1/routes/reorder \
  -H "Authorization: Bearer $PRIMITIVE_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "updates": [{ "id": "<route-uuid>", "priority": 50 }] }'


# change or disable a route
curl -X PATCH https://api.primitive.dev/v1/routes/<route-uuid> \
  -H "Authorization: Bearer $PRIMITIVE_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'


# remove a route
curl -X DELETE https://api.primitive.dev/v1/routes/<route-uuid> \
  -H "Authorization: Bearer $PRIMITIVE_AUTH_TOKEN"

From the CLI

Every operation above is a primitive routes verb, generated from the same API spec:

primitive routes create-route --match-type exact \
  --pattern billing@acme.dev --function-id <function-uuid>


primitive routes list-routes


primitive routes simulate-route --recipient sales@acme.dev

The CLI prints the response data as formatted JSON. Add --envelope to see the full { success, data, meta } wrapper.

Limits

Routes are plan-limited by count. Regex matching is not generally available yet on any plan. Disabled or deleted routes are skipped at delivery; a route pointing at a deactivated endpoint falls through to the next match rather than dropping mail.