IMAP History Backfill Guide
This guide covers the IMAP history backfill API — a one-time import of a connected mailbox's historical emails, delivered to your webhook. You can import a whole mailbox (or a date window), or target a single conversation with one participant.
History backfill is currently supported only for Google (Gmail) OAuth IMAP mailboxes. Outlook/Microsoft and generic IMAP are not supported yet. The mailer you target must be a connected Gmail OAuth IMAP account with a configured webhook.
Overview
When a mailbox is connected, the live poller only forwards mail that arrives after connection. History backfill imports the historical emails that predate (or surround) that point, so a newly connected mailbox can build a complete communication timeline.
How it works:
- You start a backfill for a connected mailer. The platform walks the mailbox newest-first and delivers each email individually to the mailer's configured webhook (the same
IMAP_OAUTHincoming-email webhook used by live mail). - Delivery is at-least-once: a slice may be retried (on transient errors, redelivery, or resume), so the same email can be delivered more than once. Your webhook must dedupe by
Message-Id. - The job runs asynchronously; poll the status endpoint for progress, or abort it at any time.
Prerequisites
- A connected Gmail OAuth IMAP mailer (
mailerID). - A webhook configured on that mailer (backfilled emails are delivered there).
- A valid API token (the same auth used for other
/v1endpoints).
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /v1/imap/{mailerID}/history | Start a history backfill job |
GET | /v1/imap/{mailerID}/history/{jobId} | Get job status / progress |
POST | /v1/imap/{mailerID}/history/{jobId}/abort | Cancel an in-flight job |
The pre-rename paths /v1/{mailerID}/backfill[...] still work as aliases to the same handlers, so existing integrations keep functioning. New integrations should use the /v1/imap/{mailerID}/history paths.
See the API Reference → IMAP History Backfill section for the full generated schema.
Scope: full mailbox vs single conversation
There is no separate "mode" flag — the presence of participant decides what gets imported:
| Scope | How to select | What it imports |
|---|---|---|
| Full import | omit participant (default) | Every email in the window (and/or up to count), across the selected folders. |
| Single conversation | set participant | Only emails exchanged with that one address — from:<participant> OR to:<participant>. Use this to pull one conversation's history. |
Start a history backfill
POST /v1/imap/{mailerID}/history
| Field | Type | Required | Notes |
|---|---|---|---|
participant | string (email) | optional | A single bare email address, e.g. x@gmail.com. Provide it to import only the conversation with that address; omit for a full import. |
window_start | string (RFC3339) | see below | Oldest point to walk back to, e.g. 2025-01-01T00:00:00Z. |
window_end | string (RFC3339) | optional | Newest point. Defaults to now. |
count | integer | optional | Cap on number of emails (newest-first). 0/omitted = no cap; bounded only by the window. Clamped to a server maximum (default 1000). |
folders | object | optional | Include non-INBOX folders: { "inbox": true, "sent": false, "spam": false, "trash": false }. Defaults to inbox-only. |
When is window_start required?
- Full import — provide at least one of
window_startorcount. - Single conversation (
participantset) —window_startis optional. If omitted, it defaults to the last 1 year. (window_endstill defaults to now.)
Examples
Full backfill of a date window (inbox + sent):
curl -X POST "https://<your-host>/v1/imap/{mailerID}/history" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"window_start": "2025-01-01T00:00:00Z",
"count": 1000,
"folders": { "sent": true }
}'
Single conversation with one participant (defaults to the last year):
curl -X POST "https://<your-host>/v1/imap/{mailerID}/history" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"participant": "x@gmail.com"
}'
{ "success": true, "jobId": "01JC3BBW8S9YGX2VNKG5MD7BTA" }
Idempotent per mailer: only one active backfill per mailer at a time. A second request while one is in flight returns 409 with the existing jobId:
{ "success": false, "error": "active history backfill already exists", "jobId": "01JC3BBW8S9YGX2VNKG5MD7BTA" }
Check status
GET /v1/imap/{mailerID}/history/{jobId}
{
"success": true,
"jobId": "01JC3BBW8S9YGX2VNKG5MD7BTA",
"status": "processing",
"processed": 250,
"target": 1000,
"percent": 25,
"startedAt": "2026-06-03T10:07:23Z",
"endedAt": ""
}
status—pending→processing→done(orfailed/cancelled).percent— time-based progress over the requested window (clamped 0–100;100when done).processed/target— emails delivered so far / the count cap (0= uncapped).endedAt— set once the job reaches a terminal state.
The job must belong to the addressed mailerID; a job from another mailer (or another tenant) returns 404.
Abort a history backfill
POST /v1/imap/{mailerID}/history/{jobId}/abort
Cancels an in-flight job; the worker stops cleanly at its next slice, leaving the job cancelled. Already-finished jobs (done/failed/cancelled) return 409.
{ "success": true, "jobId": "01JC3BBW8S9YGX2VNKG5MD7BTA", "status": "cancelled" }
After a job is cancelled (or finished), you can start a new backfill for the same mailer. Emails already delivered before cancellation stay delivered (dedupe by Message-Id).
Webhook delivery & dedupe
- Each historical email is delivered as a normal incoming-email webhook (
IMAP_OAUTH), with the full email content inline. - Backfill deliveries carry an
X-History-Backfill-Job-Idheader so you can distinguish them from live mail. - Because delivery is at-least-once, dedupe by
Message-Idon your side — this is required, not optional.
Lifecycle events (optional)
Alongside the per-email deliveries, the job emits small lifecycle events to the same webhook so you can track it end-to-end:
HISTORY_BACKFILL_STARTED— when the first slice begins.HISTORY_BACKFILL_PROGRESS— throttled progress updates (~every 10%).HISTORY_BACKFILL_COMPLETED— on success.HISTORY_BACKFILL_FAILED— on terminal failure.
Notes & limits
- Gmail only (for now): see the provider-support note above. A non-Gmail mailer returns 422.
- Throughput is bounded by Gmail's per-user API quota (~3,000 messages/min) and by your webhook endpoint's speed; large mailboxes take minutes. Use the status endpoint to track progress.
- Window precision is rounded to whole seconds (Gmail's date filter is day-granular).
- Single-conversation matching uses Gmail's
from:/to:search on the participant address; it imports the messages Gmail returns for that query within the window.