Email Sync
Trigger manual IMAP sync for connected email accounts to fetch latest messages
MisarMail syncs connected IMAP accounts automatically in the background. The sync API lets you trigger an immediate sync on demand — for example, when a user clicks a "Refresh" button in your inbox UI.
Requires an active dashboard session. These routes are cookie-authenticated and are not part of the msk_ API-key surface.
Trigger a sync
/api/syncTrigger an immediate IMAP sync for one connected account.
Request body
accountIdstringbodyrequiredUUID of the IMAP account to sync. The account must belong to the current user and have IMAP configured.
folderstringbodydefault: INBOXMailbox folder to sync. Must be an allowed IMAP folder name (e.g. INBOX, Sent, Drafts, Trash, Spam, Archive, Junk, or their INBOX.* / [Gmail]/* variants).
fullSyncbooleanbodydefault: falseWhen true, fetch the full folder history instead of only messages since the last sync.
limitintegerbodydefault: 100Maximum number of messages to fetch (1–200).
Response fields
successbooleantrue when the sync completed.
messagestringHuman-readable status ("Sync completed").
statsobjectSync results: newEmails (number of messages inserted), folder (the folder that was synced), and syncedAt (ISO-8601 timestamp).
curl -X POST /api/sync \
-H "Content-Type: application/json" \
-d '{ "accountId": "3f8a1c2e-0b4d-4e6f-9a1b-2c3d4e5f6a7b" }'async function handleRefreshClick(accountId: string) {
const res = await fetch('/api/sync', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ accountId }),
});
if (res.status === 409) {
// Sync already in progress — treat as a benign skip, not an error.
return;
}
const { stats } = await res.json();
if (stats?.newEmails > 0) {
// reload inbox list
}
}{
"success": true,
"message": "Sync completed",
"stats": {
"newEmails": 4,
"folder": "INBOX",
"syncedAt": "2025-06-14T11:05:30Z"
}
}Concurrent syncs
Only one sync can run per account at a time. Before starting, the endpoint atomically claims the account by setting its sync_status to syncing. If the account is already mid-sync (for example, the background worker or a concurrent "Sync All" claimed it first), the request is rejected:
{
"success": false,
"error": "Sync already in progress",
"alreadySyncing": true
}409 with alreadySyncing: true is a benign skip, not a failure — another sync is already covering this account. A stuck syncing claim self-heals: if a sync has been marked syncing for more than 5 minutes (e.g. a crashed run), the next request is allowed to reclaim it.
Do not poll this endpoint. Use it only in response to an explicit user action (e.g. a refresh-button click). Continuous polling collides with the background sync and the concurrency guard.
Other responses
| Status | Body | Cause |
|---|---|---|
400 | { success: false, error: "Validation failed", details } | Invalid body (bad accountId, unknown folder, out-of-range limit) |
400 | { success: false, error: "Account does not have IMAP configured" } | Account has no IMAP host or stored credentials |
401 | { success: false, error: "Unauthorized" } | No active session |
404 | { success: false, error: "Account not found" } | No such account for this user |
500 | { success: false, error: "<message>" } | IMAP connection/auth failure or unexpected error |
Get sync status
/api/syncReturns the current sync state and per-folder message counts for a single account.
Query parameters
accountIdstringqueryrequiredUUID of the account to inspect. Returns 400 if omitted.
Response fields
successbooleantrue when the request succeeded.
accountobjectThe account's sync state: id, email, lastSyncAt, and syncStatus.
statsobjectfolderCounts — a map of folder name to { total, unread } (only folders with messages are included) — and totalUnread, the sum of unread counts across folders.
curl "/api/sync?accountId=3f8a1c2e-0b4d-4e6f-9a1b-2c3d4e5f6a7b" \
-H "Cookie: <session>"{
"success": true,
"account": {
"id": "3f8a1c2e-0b4d-4e6f-9a1b-2c3d4e5f6a7b",
"email": "hello@example.com",
"lastSyncAt": "2025-06-14T11:05:30Z",
"syncStatus": "idle"
},
"stats": {
"folderCounts": {
"inbox": { "total": 128, "unread": 3 },
"sent": { "total": 42, "unread": 0 }
},
"totalUnread": 3
}
}syncStatus values:
| Value | Meaning |
|---|---|
idle | Last sync completed successfully; no active sync |
syncing | A sync is currently in progress |
pending | Freshly provisioned account not yet synced |
error | Last sync attempt failed (check IMAP credentials or server availability) |
Auto-sync behaviour
| Trigger | Behaviour |
|---|---|
| Background auto-sync | Connected accounts are synced automatically on a periodic schedule |
Trigger via POST /api/sync | Immediate; blocked by the per-account concurrency guard while a sync is running |
| New account connected | Sync triggered automatically |
For new accounts (or a fullSync), MisarMail fetches recent history; otherwise it fetches messages since lastSyncAt (with a short look-back buffer to catch re-filed or back-dated messages). Synced messages are de-duplicated on (account_id, message_id), so re-running a sync is idempotent.