Mail guides
06 · Operations

Ship, observe, debug.

The message log is your audit trail and your debugging surface. Plus attachments, batch send patterns, and the right-to-be-forgotten deletion endpoint.


1

The message log

Every message Mail sends gets a row in the workspace-scoped message log. Outbound only (inbound messages live under the destination mailbox). Listable, filterable, exportable.

GET /workspaces/:ws/messages
curl 'https://api.mail.productcraft.co/v1/workspaces/<ws>/messages?template=welcome&status=delivered&limit=20' \
  -H 'authorization: Bearer pcft_live_...'
{
  "data": [
    {
      "id":         "msg_a1b2...",
      "to":         "ada@example.com",
      "from":       "noreply@acme.com",
      "template":   "welcome",
      "subject":    "Welcome to Acme, Ada!",
      "status":     "delivered",
      "sent_at":    "2026-05-11T11:38:42Z",
      "delivered_at": "2026-05-11T11:38:44Z",
      "metadata":   { "user_id": "...", "trigger": "signup" }
    }
  ],
  "pagination": { "next_cursor": "...", "has_more": true }
}

Query params: template, status (queued / sent / delivered / bounced / complained / deferred / suppressed), since, until, to, from_mailbox_id.


2

Per-message detail

GET /workspaces/:ws/messages/:id
curl https://api.mail.productcraft.co/v1/workspaces/<ws>/messages/msg_a1b2... \
  -H 'authorization: Bearer pcft_live_...'

Returns the structured shape plus a timeline of state transitions (queued → sent → delivered, or queued → sent → bounced). For the actual rendered HTML/text body of a message, the body is gated separately:

GET /workspaces/:ws/messages/:id/body
curl https://api.mail.productcraft.co/v1/workspaces/<ws>/messages/msg_a1b2.../body \
  -H 'authorization: Bearer pcft_live_...'

Why a separate endpoint? Message bodies often contain PII — reset links with one-time codes, billing amounts, account details. The mail.message.body.read permission is distinct from mail.message.read so you can grant message-list access to support without exposing message contents.


3

Message timeline

A consolidated view of every event for a single message — useful for support debugging 'why didn't my customer get this?'

GET /workspaces/:ws/messages/timeline?id=msg_a1b2...
{
  "message_id": "msg_a1b2...",
  "events": [
    { "kind": "queued",    "at": "2026-05-11T11:38:42Z" },
    { "kind": "sent",      "at": "2026-05-11T11:38:43Z", "smtp_relay": "shared" },
    { "kind": "delivered", "at": "2026-05-11T11:38:44Z", "remote_mx": "gmail-smtp-in.l.google.com" },
    { "kind": "opened",    "at": "2026-05-11T11:42:01Z", "user_agent": "..." }
  ]
}

4

Export the message log

For compliance + analytics, the CSV export endpoint is the efficient path:

GET /workspaces/:ws/messages/export?since=...&template=...
curl -o messages.csv \
  'https://api.mail.productcraft.co/v1/workspaces/<ws>/messages/export?since=2026-05-01T00:00:00Z' \
  -H 'authorization: Bearer pcft_live_...'

Throttled — one export per minute per workspace. For large workspaces with millions of messages, exports paginate internally and the response includes a continuationURL.


5

Attachments

Inline base64 in the send call. Up to 10 attachments per message; total size under 10 MB.

POST /workspaces/:ws/templates/invoice/send (with attachment)
curl https://api.mail.productcraft.co/v1/workspaces/<ws>/templates/invoice/send \
  -H 'authorization: Bearer pcft_live_...' \
  -H 'content-type: application/json' \
  -d '{
    "to": "ada@example.com",
    "from_mailbox_id": "<billing-mailbox-id>",
    "data": { "name": "Ada", "amount": "$29" },
    "attachments": [
      {
        "filename":    "invoice-2026-05.pdf",
        "content_type": "application/pdf",
        "content":     "<base64-encoded-bytes>"
      }
    ]
  }'

Big PDFs hit the size limit fast. For invoices over a few hundred KB, host the file on your own CDN and link to it in the email body instead. Better UX too — recipient opens on phone, sees the link, doesn't have to download to view.


6

Right-to-be-forgotten deletion

GDPR / CCPA workflow — a recipient asks you to delete every record of them. Mail exposes a workspace-scoped bulk delete by email address.

DELETE /workspaces/:ws/messages?recipient=ada@example.com
curl -X DELETE \
  'https://api.mail.productcraft.co/v1/workspaces/<ws>/messages?recipient=ada@example.com' \
  -H 'authorization: Bearer pcft_live_...'

Irreversible. Removes every outbound message log row for that recipient address from this workspace. Audit-log entry is preserved (action + actor + timestamp + count); the actual message content + metadata is purged.

Doesn't touch the suppression list — the recipient stays suppressed (you don't want a bounced address to start bouncing again). Remove from suppression separately if needed.


7

Debugging guide

When a customer reports “I didn't get the email,” walk through these in order:

  1. Is the address suppressed? GET /suppression?email=.... If yes, reason + timestamp tells you why. Resolve with them (correct the address) or remove from suppression with their consent.
  2. Did we send? GET /messages?to=ada@example.com&template=welcome. If no row, your application never called send. Bug in your code.
  3. Did delivery succeed? Inspect the message timeline. delivered means receiver accepted; the email is in their world. bounced means receiver rejected with a reason — share the diagnostic with the customer.
  4. If delivered but recipient says missing — it's in their spam folder or their corporate filter quarantined it. Tell them to check spam; have them whitelist your domain.
  5. If “deferred” for hours — receiver throttling. Patient retries usually clear it; if it deferred for >6h, the receiver almost certainly tarpit-ed you. Worth opening a support ticket if it persists.

What's next

You've finished the series

From here:

  • API reference for every endpoint with full request/response schemas.
  • Auth guide 07 for the Mail-integrated verification + reset shortcut.
  • Platform docs if you want to automate Mail setup (mint PAK, create mailboxes, deploy templates from CI).