Mail guides
06 · Operations

Ship, observe, debug.

The message log is your audit trail and your debugging surface. Plus CSV export 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":            "e89a0605-c5ce-4e13-9755-4d4d8dc33ae1",
      "workspace_id":  "<ws>",
      "domain_id":     "77aa9383-...",
      "from_address":  "noreply@acme.com",
      "to_address":    "ada@example.com",
      "subject":       "Welcome to Acme, Ada!",
      "template_name": "welcome",
      "status":        "delivered",
      "smtp_transcript": "250 2.0.0 Ok: queued as B701E3F169",
      "spf_result":    null,
      "dkim_result":   null,
      "dmarc_result":  null,
      "bounce_code":   null,
      "bounce_detail": null,
      "created_at":    "2026-05-11T11:38:42.660Z",
      "delivered_at":  "2026-05-11T11:38:42.872Z"
    }
  ],
  "pagination": { "has_more": true, "next_cursor": "eyJjcmVh..." }
}

Query params: recipient, template, status (queued / sent / delivered / bounced / complained / failed / suppressed), subject_contains, plus limit and cursor for pagination.


2

Per-message detail

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

Returns the same shape as the list row: current status plus the delivery evidence — smtp_transcript, SPF/DKIM/DMARC verdicts, and bounce_code + bounce_detail when the receiver rejected. The rendered HTML/text body is gated separately:

GET /workspaces/:ws/messages/:id/body
curl https://api.mail.productcraft.co/v1/workspaces/<ws>/messages/<message-id>/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. It adds body_html + body_text to the detail shape — both come back null if the workspace has redact_bodies enabled (toggle via PATCH /workspaces/:ws/messages/settings).


3

Volume timeline

Day-bucketed send volume for the workspace — the quick 'did our send rate fall off a cliff / did bounces spike' chart.

GET /workspaces/:ws/messages/timeline
curl 'https://api.mail.productcraft.co/v1/workspaces/<ws>/messages/timeline?granularity=day&since=2026-05-01T00:00:00Z&until=2026-05-04T00:00:00Z&metric=sent' \
  -H 'authorization: Bearer pcft_live_...'
{
  "granularity": "day",
  "metric": "sent",
  "points": [
    { "date": "2026-05-01", "count": 412 },
    { "date": "2026-05-02", "count": 398 },
    { "date": "2026-05-03", "count": 0 }
  ]
}

granularity (only day today), since, and until are required; metric is sent (default) or bounced. Days with zero messages still appear with count: 0.


4

Export the message log

For compliance + analytics, the CSV export endpoint streams the whole filtered log in one response. Same filters as the list endpoint (recipient, template, status, subject_contains):

GET /workspaces/:ws/messages/export
curl -o messages.csv \
  'https://api.mail.productcraft.co/v1/workspaces/<ws>/messages/export?status=bounced' \
  -H 'authorization: Bearer pcft_live_...'

Rate-limited: one concurrent export and five per hour per workspace (excess returns 429). Message bodies are intentionally omitted from the CSV — they stay gated behind mail.message.body.read.


5

Attachments

Outbound sends don't support attachments — the send body takes to, from, data, and an optional subject, nothing else. Host the file (invoice PDF, report, export) on your own storage/CDN and link to it in the email body. That's better for deliverability and UX anyway — attachment-heavy mail trips spam filters, and a link opens instantly on a phone.


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_...'

# → { "deleted": 42 }

Irreversible. Removes every outbound message log row for that recipient address (matched case-insensitively) from this workspace and returns the deleted-row count. Requires mail.message.delete.

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 lists the workspace's suppressed recipients with reason + timestamp. Sends to a suppressed address are rejected with 422 at send time — no log row is written. Correct the address, or remove it from suppression with the recipient's consent.
  2. Did we send? GET /messages?recipient=ada@example.com&template=welcome. If no row, your application never called send (or the send call errored — check your own logs).
  3. Did delivery succeed? Fetch the message detail. delivered means the receiver accepted; the email is in their world. bounced means the receiver rejected — bounce_code + bounce_detail carry the reason to share 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 stuck in queued or sent queued means the message is accepted but not yet relayed; sent means the relay accepted it and the receiver's verdict is pending. Both normally resolve to delivered or bounced quickly; a message stuck there for hours is worth a support ticket.

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).