> ## Documentation Index
> Fetch the complete documentation index at: https://pigeonpost-developer.27communication.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Initial sync (import existing mail)

> Import up to the last 30 days of mail already in a mailbox when it connects, without email.new webhooks.

By default, a connected account syncs **only the mail that arrives after it connects**. Mail that was already in the mailbox isn't imported.

Turn on **initial sync** when you also need the recent mail that is already there. The engine then imports the **last 30 days** (or fewer, if you choose) right after the account connects.

<Info>
  **At a glance**

  * Off by default. Turn it on in the auth intent's `config` with `initial_sync_enable: true`, or with the initial sync option in the dashboard.
  * Imports the last **1 to 30 days**. Default and **maximum: 30 days**.
  * Works for **IMAP** accounts (Gmail included), for **new accounts** only (never on a reconnect).
  * Imported mail sends **no** `email.new` webhooks. You get `account.initial_sync.running` and `account.initial_sync.completed`, then read the mail with [List emails](/emails/list).
  * Mail that arrives during the import is new mail and sends `email.new` as usual.
</Info>

## Turn it on

### With the API

Add a `config` to the [auth intent](/accounts/imap) that connects the mailbox:

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST "$EE_URL/api/v1/auth/intent" \
    -H "X-API-KEY: $EE_KEY" -H "Content-Type: application/json" \
    -d '{
      "provider": "imap",
      "email": "ana@fastmail.com",
      "password": "app-password",
      "state": "user-42",
      "config": { "initial_sync_enable": true, "initial_sync_days": 14 }
    }'
  ```

  ```php PHP theme={null}
  $response = $http->post("$baseUrl/api/v1/auth/intent", [
      'headers' => ['X-API-KEY' => $apiKey],
      'json' => [
          'provider' => 'imap',
          'email' => $email,
          'password' => $password,
          'state' => (string) $user->id,
          'config' => ['initial_sync_enable' => true, 'initial_sync_days' => 14],
      ],
  ]);
  ```

  ```js Node.js theme={null}
  const res = await fetch(`${baseUrl}/api/v1/auth/intent`, {
    method: "POST",
    headers: { "X-API-KEY": apiKey, "Content-Type": "application/json" },
    body: JSON.stringify({
      provider: "imap",
      email,
      password,
      state: String(user.id),
      config: { initial_sync_enable: true, initial_sync_days: 14 },
    }),
  });
  ```
</CodeGroup>

In this example, the mailbox imports the last 14 days.

<ParamField body="config.initial_sync_enable" type="boolean" default="false">
  Import the mail already in the mailbox.
</ParamField>

<ParamField body="config.initial_sync_days" type="integer" default="30">
  How many days back to import, from **1 to 30**. More than 30 is refused with `400 errors/invalid_parameters`. Ignored unless `initial_sync_enable` is `true`.
</ParamField>

The response is the account (`201`) with an `initial_sync` object (`"status": "pending"`). The import runs in the background.

### From the dashboard

In **Accounts → Add account**, turn on the initial sync option and choose how many days to import (1 to 30). See [From the dashboard](/accounts/imap#from-the-dashboard).

## What happens

Once the user connects the mailbox, these steps run on their own:

1. **The account connects.** You get `account.add`. The account's `initial_sync.status` is `pending`.
2. **The import starts**, within a few seconds. You get `account.initial_sync.running`.
3. **The engine imports** the mail received in the chosen window, from every folder: Inbox, Sent, Archive, labels and so on. Each email is stored like any synced email, with its attachments. **No `email.new` is sent for these.**
4. **The import finishes.** You get `account.initial_sync.completed`, with the number of emails imported. The account's `initial_sync.status` is `completed`.
5. **Read the imported mail** with [List emails](/emails/list), for example `GET /api/v1/emails?account_id=acc_…`.

From the moment the account connects, it also syncs new mail as usual. An email that arrives while the import is still running is **new mail**: it sends `email.new`, and it is never counted as imported.

How long the import takes depends on the size of the mailbox: usually seconds to a few minutes for 30 days.

<Note>
  Why no `email.new` for imported mail? A mailbox can hold thousands of emails from the last 30 days. Sending a webhook for each would flood your endpoint and look like new mail. Use `account.initial_sync.completed` as your signal to fetch the imported mail in one go.
</Note>

## Follow the import

### On the account

Every account connected with initial sync has an `initial_sync` object, in [Retrieve an account](/accounts/status) and [List accounts](/accounts/status). Accounts connected without it don't have this field.

```json theme={null}
{
  "object": "Account",
  "id": "acc_ba1fa6938d8548298e8ce62e4b4b4e99",
  "user_id": "ana@acme.com",
  "provider": "imap",
  "status": "running",
  "initial_sync": {
    "status": "completed",
    "started_at": "2026-10-06T09:01:32Z",
    "completed_at": "2026-10-06T09:03:10Z",
    "days": 30,
    "imported": 412
  },
  "…": "…"
}
```

| Field | |
| - | - |
| `status` | `pending`: connected, the import hasn't started yet. `running`: importing. `completed`: done. `failed`: gave up ([below](#if-the-import-fails)) |
| `started_at` | When the import started. `null` while `pending` |
| `completed_at` | When it completed or failed. `null` before that |
| `days` | How many days back it imports |
| `imported` | How many emails it imported. Set when it completes or fails |

### With webhooks

Subscribe your [webhook endpoint](/webhooks/overview) to these events:

| Event | When |
| - | - |
| `account.initial_sync.running` | The import started |
| `account.initial_sync.completed` | The import finished. Fetch the mail now |
| `account.initial_sync.failed` | The import gave up |

`data.initial_sync` is the account's `initial_sync` object:

```json account.initial_sync.completed theme={null}
{
  "object": "Event",
  "id": "evt_5d6e7f8a9b0c41d2e3f4a5b6c7d8e9f0",
  "type": "account.initial_sync.completed",
  "created_at": "2026-10-06T09:03:10Z",
  "account_id": "acc_ba1fa6938d8548298e8ce62e4b4b4e99",
  "endpoint_id": "we_e3f1a2b4c5d64e7f8a9b0c1d2e3f4a5b",
  "data": {
    "initial_sync": {
      "status": "completed",
      "started_at": "2026-10-06T09:01:32Z",
      "completed_at": "2026-10-06T09:03:10Z",
      "days": 30,
      "imported": 412
    }
  }
}
```

## If the import fails

If the provider can't be read, for example because of a network error, the engine tries again on its next pass. Each try picks up where the last one stopped, so nothing is imported twice.

After **5 failed tries**, the import gives up:

* `initial_sync.status` becomes `failed`;
* `imported` shows what was imported until then (that mail stays);
* you get `account.initial_sync.failed`.

A failed import doesn't affect the account: new mail keeps syncing and sending `email.new`. If the account itself has a problem (for example, its password changed), its [status](/accounts/status) shows it as usual.

## Good to know

* **30 days at most.** Older mail can't be imported.
* **Only when a new account connects.** Reconnecting an account doesn't import again. Connecting a mailbox that is already connected refreshes its credentials and doesn't import either.
* **"Days" count from when the import starts.** IMAP servers (Gmail included) search by day, so the whole first day of the window is included.
* **Every folder is imported,** including Sent, Spam and Trash, as in a normal sync. Gmail's "All Mail", "Starred" and "Important" views are skipped; an email with several Gmail labels is imported once per label (see [Gmail](/accounts/gmail#labels-are-folders)).
* **Unipile compatibility.** The names are the same as in Unipile v2: `initial_sync_enable`, the account's `initial_sync`, the `account.initial_sync.*` events. Unipile offers initial sync for IMAP; the same here. `initial_sync_days` is our addition.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.