Skip to main content
Every email Parser receives is tracked, on every path: a forwarding address, a Gmail, Microsoft 365 or IMAP mailbox, or POST /v1/intake/email (signed by a mail provider, or with an API key). Ignored and refused emails are tracked too, so your CRM can account for every message that came in, not only the ones that became jobs.

The email id

Each email gets an id like em_3f1c0e9a6b2d4c8e1a7f5b90. It is stable: a redelivery of the same message (an Email Worker retry, a provider pushing it again, a mailbox resync) is the same email, never a second one. The id comes back:
  • in the X-Email-ID header of every POST /v1/intake/email answer, and as email_id in an ignored email’s body;
  • as email_id in each row of GET /v1/mailboxes/{id}/messages;
  • in a job’s intake.email.email_id: the email that started the job (also in every job event);
  • in the email.received webhook.

The record

GET /v1/emails/{email_id} (jobs:read):
The body is never part of the record or of any event. GET /v1/emails lists emails newest first, filtered by mailbox_id, thread_id, tag, outcome (job, merged, ignored for any reason, one ignored:<reason>, refused), sender, since and job_id, paged with cursor.

The raw .eml

GET /v1/emails/{email_id}/raw returns the message exactly as it was received, byte for byte, as message/rfc822 with Content-Disposition: attachment. It is stored as job files are, encrypted with your tenant’s own key. store_raw_email is a mailbox setting, on by default:
PATCH replaces the mailbox’s whole settings: send the others you set too. An email posted with an API key (no mailbox) keeps its raw message. Messages are capped at 64 MB on every path.

Threads

A deal arrives as a thread: the submission, internal forwards, questions back, replies that add a statement. GET /v1/emails/threads/{thread_id} lists every tracked email of the thread in the order they arrived, each with its outcome and job:
Here the reply added the missing statement, so it started job-2 with every document of the thread (job-1’s superseded_by names it), and the forward brought nothing new. From a job, intake.email.thread_id is its thread, and GET /v1/emails?job_id= lists the emails that point at it. GET /v1/emails/threads/{thread_id}/raw downloads the thread’s kept messages as one mbox file (mboxrd), in order. A thread id contains /; put it in the path as it is.

Tags

Tag emails with your own labels, e.g. client:lifetime or stage:funded. A tag is trimmed and lowercased, then must be 1 to 64 of a-z 0-9 _ : -; an email has at most 20. Writing tags needs mailboxes:manage, reading them jobs:read. A mailbox’s default_tags setting tags every email it receives, as it arrives:
Every tag change is in the audit log (email.tags, with the tags the email ended with). An invalid tag, or one past 20, is refused with 422.

Erasure and retention

  • DELETE /v1/emails/{email_id} erases one email: its record, its raw .eml and its email.received events.
  • DELETE /v1/emails/threads/{thread_id} erases every email of a thread the same way.
  • DELETE /v1/jobs/{job_id} (and DELETE /v1/jobs?end_user_id=) erases, with the job, the emails that fed it: the emails whose job it is (the one that started it, those merged into it, those that arrived while it was the thread’s job), and the thread’s earlier emails carrying a document it holds (a reply’s job gathers the thread’s earlier attachments). So in the thread above, erasing job-2 erases all three emails, while erasing only the superseded job-1 erases the submission alone and leaves the reply and the forward with job-2.
  • A tenant’s retention_days erases emails older than it, with their raw messages, as it erases old jobs.
Erasing an email does not erase its job; erasing a job does not erase a later job of its thread. Erasures need jobs:delete and are in the audit log (email.delete, email.delete_thread, job.delete). Another tenant’s email or thread answers 404.