Skip to main content

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.

Provider support

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_OAUTH incoming-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

  1. A connected Gmail OAuth IMAP mailer (mailerID).
  2. A webhook configured on that mailer (backfilled emails are delivered there).
  3. A valid API token (the same auth used for other /v1 endpoints).

Endpoints

MethodPathPurpose
POST/v1/imap/{mailerID}/historyStart a history backfill job
GET/v1/imap/{mailerID}/history/{jobId}Get job status / progress
POST/v1/imap/{mailerID}/history/{jobId}/abortCancel an in-flight job
Legacy paths

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:

ScopeHow to selectWhat it imports
Full importomit participant (default)Every email in the window (and/or up to count), across the selected folders.
Single conversationset participantOnly 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

FieldTypeRequiredNotes
participantstring (email)optionalA 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_startstring (RFC3339)see belowOldest point to walk back to, e.g. 2025-01-01T00:00:00Z.
window_endstring (RFC3339)optionalNewest point. Defaults to now.
countintegeroptionalCap on number of emails (newest-first). 0/omitted = no cap; bounded only by the window. Clamped to a server maximum (default 1000).
foldersobjectoptionalInclude 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_start or count.
  • Single conversation (participant set) — window_start is optional. If omitted, it defaults to the last 1 year. (window_end still 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": ""
}
  • statuspendingprocessingdone (or failed / cancelled).
  • percent — time-based progress over the requested window (clamped 0–100; 100 when 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-Id header so you can distinguish them from live mail.
  • Because delivery is at-least-once, dedupe by Message-Id on 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.