IMAP: drafts pile up in the thread because every client autosave creates a new row and old drafts are never removed
Summary
On a self-hosted instance with an IMAP mailbox, a single reply appears as several draft rows in the email thread. Each save in the mail client adds one more row, and the old rows never disappear.
The IMAP driver never reports deleted (expunged) messages, and the import path only inserts, so it never updates or removes an existing row.
Environment
- Twenty v2.39.0, self-hosted with Docker, image
twentycrm/twenty:v2.39.0 - Mail server: Dovecot, IMAP over TLS on port 993
- Mail client: hoster webmail client (not sure what the use)
- The server advertises
CONDSTORE,QRESYNC,UIDPLUSandMOVEafter login
What I see
The thread timeline in twenty shows each draft autosave as a new row. The IMAP messages behind those rows no longer exist. A UID SEARCH for that subject in Drafts returns nothing.
Expected behavior
One draft row that holds the latest version, removed once the mail is sent or the draft deleted.
Technical inputs
Reproduce:
- Connect an IMAP mailbox whose client rewrites a draft on every save, meaning it appends a new message and expunges the old one. The trace below shows what that looks like.
- Write a mail in the client and save it a few times, or let it autosave.
- Let Twenty run its message import between the saves.
- Open the thread in Twenty.
IMAP trace:
Read-only test with EXAMINE and UID FETCH on one new draft.
| Time (UTC) | Drafts HIGHESTMODSEQ |
Event |
|---|---|---|
| 12:40:15 | 643 | new uid 2884, flags (\Seen \Draft), message-id <1297733633.357470...> |
| 12:41:20 | 651 | new uid 2886 <239981072.357555...>, uid 2884 gone |
| 12:42:30 | 660 | new uid 2889 <500785936.357672...>, uid 2886 gone |
| 12:44:05 | 666 | new uid 2891 <1955339940.357847...>, uid 2889 gone |
| 13:08:06 | 672 | new uid 2893 <856918606.359828...>, uid 2891 gone |
UIDVALIDITY stayed at 1752657694 for the whole test. Every save creates a new UID and a new Message-ID, and the server deletes the previous one. The client skipped some UIDs in between, 2885, 2887 and 2888, which were further autosaves. The folder holds one message, but it already sits at UID 2893, so this client has created and deleted roughly 2,900 drafts in that folder.
Neither the external id, which is folder:uid, nor headerMessageId stays stable across a draft save. Every save looks like a new message to Twenty.
Happy to share more data privately if it helps fix the bug.
Claudes suggestion:
haven't checked it, because I'm not that good of a coder. Paths below are relative to
packages/twenty-server/src/modules/messaging/.
message-import-manager/drivers/imap/services/imap-get-message-list.service.ts
returns a hardcoded messageExternalIdsToDelete: [] in both return paths, at
L109-115 and L157-163. It builds external ids as ${prefix}:${uid} at L153-155.
message-import-manager/drivers/imap/services/imap-sync.service.ts only looks
forward. fetchNewMessageUids at L57-77 does this:
const lastSyncedUid = previousCursor?.highestUid ?? 0;
if (lastSyncedUid >= maxUid) { return []; }
const uidRange = `${lastSyncedUid + 1}:${maxUid}`;
validateUidValidity at L37-55 only throws when UIDVALIDITY changes. The driver
handles no expunge and no QRESYNC VANISHED response, although this server
advertises QRESYNC.
The import then only inserts. services/messaging-message-list-fetch.service.ts
at L162-181 skips every external id that already has an association, and
services/messaging-message.service.ts at L160-201 and L261 inserts only when
neither a message, matched by headerMessageId, nor an association exists. It
never updates isDraft, text or receivedAt on a row that already exists.
Five saves therefore produce five rows, and Twenty deletes or refreshes none of them.
Source: twentyhq/twenty