MOLTED EMAIL

Send Diagnostics

Read-only operator endpoints for diagnosing why a tenant's outgoing email is not sending.

When someone reports "our emails are not going out", the answer is usually one of five things: they were blocked by policy, deferred by domain warmup, held for approval, orphaned by a failed worker job, or attributed to a mailbox nobody was watching. The diagnostics endpoints answer each of those directly.

All routes are GET, all queries are SELECT, and nothing here mutates state.

Authentication

These routes accept ADMIN_DIAGNOSTICS_KEY as a bearer token:

curl
curl "https://api.molted.email/v1/admin/diagnostics/sends?tenantId=TENANT_ID" \
  -H "Authorization: Bearer YOUR_DIAGNOSTICS_KEY"

Set ADMIN_DIAGNOSTICS_KEY to a value distinct from ADMIN_API_KEY. The diagnostics key unlocks only these read-only routes, so it can be handed to whoever (or whatever) is triaging an incident without also granting provisioning, billing, and quota mutation. When ADMIN_DIAGNOSTICS_KEY is unset, the routes fall back to accepting ADMIN_API_KEY.

Read-only does not mean low-sensitivity. These routes are not tenant-scoped by credential: tenantId is a query parameter, so the key reads any tenant on the platform. The responses include recipient email addresses (/orphans) and mailbox addresses (/mailboxes). No message bodies, subjects, payloads, or tokens are exposed, but treat the key as granting cross-tenant access to recipient PII, not merely to counters.

Every route requires a tenantId. Routes that accept a window take 24h, 7d, or 30d, defaulting to 7d.

Where to start

GET /v1/admin/diagnostics/sends returns the status histogram. The status and deliveryStatus pair is what separates a healthy send from a stuck one.

Response
{
  "tenantId": "t_acme",
  "window": "7d",
  "total": 79,
  "byStatus": [
    { "status": "accepted", "deliveryStatus": "scheduled", "count": 63 },
    { "status": "blocked",  "deliveryStatus": null,        "count": 9 },
    { "status": "accepted", "deliveryStatus": "queued",    "count": 4 },
    { "status": "sent",     "deliveryStatus": "delivered", "count": 3 }
  ]
}

Read it like this:

PairMeaningNext step
sent / deliveredHealthy.Nothing to do.
accepted / queuedIn flight.Check /orphans if the count is not draining.
accepted / scheduledDeferred to a future date.Check /warmup.
pending_approvalHeld for human review.Check the mailbox autonomy level via /mailboxes.
blockedRejected by policy.Check /blocks for the reason.

Warmup

GET /v1/admin/diagnostics/warmup explains scheduled sends. A sender domain still in warmup has a daily cap, and non-transactional sends that exceed it are deferred to a future date rather than failing - the send reports success and goes out days later.

Response
{
  "tenantId": "t_acme",
  "deferHorizonDays": 30,
  "domains": [
    {
      "domain": "mail.acme.com",
      "tracked": true,
      "warmupOverride": false,
      "firstSendAt": "2026-08-01T19:34:17.017Z",
      "daysSinceFirstSend": 3,
      "dailyLimit": 100,
      "usedToday": 100,
      "remainingToday": 0,
      "futureReservations": [{ "slotDate": "2026-08-05", "reserved": 63, "dailyLimit": 100 }]
    }
  ],
  "deferredSends": {
    "total": 63,
    "scheduledTotal": 71,
    "reservedFutureSlots": 63,
    "earliestSlot": "2026-08-05",
    "latestSlot": "2026-08-05",
    "payloadEncrypted": false
  }
}

remainingToday: 0 with a non-empty futureReservations is the signature of a tenant whose sends are silently piling up on future dates. A dailyLimit of null means the domain is exempt (either warmupOverride is set or the domain is not tracked). Each future slot carries its own dailyLimit, because a day further out may sit in a higher warmup tier than today.

tracked: false marks a domain seen only in the send counters and absent from tenant_domains, which is what a shared sending domain looks like. Warmup does not apply to those, so their dailyLimit is null.

Reading deferredSends correctly

FieldMeaning
totalSends positively identified as warmup-deferred, or null when that cannot be determined
scheduledTotalEvery scheduled send, warmup-deferred or explicitly scheduled by the caller. Always accurate
reservedFutureSlotsWarmup slots reserved on future dates, summed across domains. Always accurate
payloadEncryptedtrue when at least one scheduled row is encrypted, which forces total to null

The warmup markers live in payload_json, which is stored as {} on deployments with CONTENT_ENCRYPTION_KEY set. On those deployments total is null rather than 0, since zero would read as a false all-clear. Use scheduledTotal and reservedFutureSlots there; both are derived from unencrypted columns.

Note the cap is per tenant and sender domain, not per mailbox. Tenants that provision one mailbox per end user share a single bucket across all of them.

Block reasons

GET /v1/admin/diagnostics/blocks ranks policy rejections by frequency.

Response
{
  "tenantId": "t_acme",
  "window": "7d",
  "total": 9,
  "reasons": [
    { "reason": "cooldown",   "count": 3, "lastSeenAt": "2026-08-04T19:34:17.017Z" },
    { "reason": "duplicate",  "count": 3, "lastSeenAt": "2026-08-04T19:34:17.017Z" },
    { "reason": "suppressed", "count": 3, "lastSeenAt": "2026-08-04T19:34:17.017Z" }
  ]
}

Most of these checks are tenant-scoped rather than mailbox-scoped: suppression, cooldown, dedupe, rate limits, and risk budgets all apply across the whole tenant. On a mailbox-per-end-user model, one user's activity can block another's.

Orphans

GET /v1/admin/diagnostics/orphans finds sends with no terminal state: rows claiming delivery_status: "queued" that never produced a send attempt, older than one hour. These are genuinely wedged - the worker job died before recording an attempt, so they will never send, never fail, and appear in no approval or override queue.

curl
curl "https://api.molted.email/v1/admin/diagnostics/orphans?tenantId=TENANT_ID&limit=100" \
  -H "Authorization: Bearer YOUR_DIAGNOSTICS_KEY"

total is the full unpaginated count; items is capped by limit (default 50, max 500) and ordered oldest first.

Per-mailbox breakdown

GET /v1/admin/diagnostics/mailboxes returns autonomy level, mailbox status, reputation, and send counts for each mailbox, plus unattributedSends.

Response
{
  "tenantId": "t_acme",
  "mailboxes": [
    {
      "mailboxId": "mb_1",
      "address": "user1@mail.acme.com",
      "status": "active",
      "autonomyLevel": 3,
      "reputationStatus": "healthy",
      "bounceRate": 0.01,
      "sends": { "total": 77, "queued": 4, "scheduled": 63, "pendingApproval": 0, "failed": 0, "blocked": 9 }
    }
  ],
  "unattributedSends": 2
}

A non-zero unattributedSends means sends arrived without a mailboxId. Those fall back to the tenant's oldest active mailbox, so on a mailbox-per-user model they are being sent from the wrong identity and charged to the wrong reputation.

autonomyLevel of 1 or 2 explains any pendingApproval count - see Autonomy Levels.

MCP tools

With MOLTED_ADMIN_KEY set, the MCP server exposes these as diagnose_sends, diagnose_warmup, diagnose_blocks, diagnose_orphans, and diagnose_mailboxes. Without that variable the tools are not registered, so normal agent runtimes never see them.

These tools are registered on the stdio transport only. The HTTP transport does not authenticate inbound requests, and tenantId is a caller-supplied argument, so registering them there would turn the port into an unauthenticated cross-tenant read endpoint. Running --transport http with MOLTED_ADMIN_KEY set logs a warning and omits the tools.

claude_desktop_config.json
{
  "mcpServers": {
    "molted-ops": {
      "command": "npx",
      "args": ["-y", "@molted/mcp"],
      "env": {
        "MOLTED_API_KEY": "mm_live_...",
        "MOLTED_ADMIN_KEY": "your_diagnostics_key",
        "MOLTED_ADMIN_API_URL": "https://api.molted.email"
      }
    }
  }
}