Automatic email management: track threads and duplicates
Track threads, versions and duplicates in automatic Gmail email management: identifiers, late replies, stale drafts and operation proof.
If you delegate the sorting or drafting of your emails to a tool, two problems come up systematically: the tool confuses a message with an action to carry out, and it processes the same event twice. Reliable automatic email management is not just a matter of a good writing model: it rests on strict identification of threads, versions and duplicates, with verifiable proof of every operation. This article gives you a concrete decision framework, a commented example and a review procedure applicable to Gmail.
Distinguishing message, thread and action
The first source of error in email automation is treating the message as the unit of work. Yet these are three different objects. The message is an individual email, with its own identifier. The thread is the conversation that groups several messages. The action is what your process must produce: file, draft a reply, follow up, mark as handled.
A single thread can contain an action already completed, an action in progress and a new action. If your system triggers "a reply" on every incoming message, it will reply several times to the same conversation. Decide explicitly, for each type of thread, which event triggers which action: the first message, every unanswered message, or only a message containing an identifiable request. A well-designed triage of requests starts with this distinction, before any tone or priority tuning.
This distinction also has an accounting consequence: if you measure time saved, an action processed twice distorts your figures as much as a missed action. Thread tracking and deduplication are therefore also prerequisites for measuring a real gain rather than an impression.
Gmail identifiers: a draft and a message are not the same thing
In the Gmail API, the identifier of a draft and the identifier of the message it contains are two distinct identifiers, and this distinction must remain exact in your system. The draft identifier is stable: it survives changes to the content. The identifier of the message contained in the draft, on the other hand, changes when the draft is modified.
If you store the message identifier to find a draft, your reference becomes invalid at the first revision. Concretely: your tool records "draft X, message M1", a colleague or the system rewrites the draft, the message becomes M2, and your tool believes the draft has disappeared. It creates a new one. You end up with two drafts on the same thread, and nobody knows which one is authoritative.
The design rule is simple: always reference the draft by its draft identifier, and treat the message identifier as volatile data, re-read on every access. The official Gmail drafts documentation describes this model; it is up to your application to draw the consequences, not up to the API to compensate.
Gmail scopes: a label is not a permission boundary
A second, often overlooked design point: the gmail.compose and gmail.modify scopes can also authorize sending messages. Restricting a gmail assistant to draft creation is therefore not guaranteed by the choice of scope alone: it requires application-level controls, in your own code, that forbid send calls.
In other words, a label — "this folder is a draft", "this label is internal" — is not a Gmail permission boundary. The real boundary is the API call your system is allowed to make. If your intention is "never send without human review", translate it into three controls: a scope as restricted as possible, an allowlist of authorized calls, and an output check that no send occurred. The Gmail scopes page helps you choose the minimal level of access; the rest is up to your design.
Example: a four-message conversation with contradictory signals
Let's take a hypothetical example, deliberately dense. An order thread with a supplier contains four messages:
- Message 1 (Monday 09:12): the supplier confirms a delivery in "week 42".
- Message 2 (Tuesday 14:30): your colleague requests an address change.
- Message 3 (Wednesday 08:45): the supplier confirms the new address, without reconfirming the date.
- Message 4 (Thursday 16:20): the supplier announces "the initial date no longer holds, new date to come".
A naive system that processes each message independently will produce contradictory conclusions: after message 3, it believes everything is confirmed; after message 4, it believes a follow-up is needed. Worse, if it prepared a confirmation draft after message 3, that draft is obsolete as of message 4.
The correct reading is at the thread level: the current state is "address confirmed, date not confirmed, awaiting a new date". The expected action is not an immediate reply but a conditional follow-up if nothing arrives. This example shows why the unit of analysis must be the thread with its history, not the latest message. An assistant that drafts replies must preserve your tone, but above all it must start from a correctly reconstructed conversation state.
Late replies and stale drafts
The stale draft is the textbook case of automatic email management. Your tool prepares a reply in the morning; the situation changes at noon; the human reviews the draft at 5 p.m. and sends it as is, because it seems "ready". The reply was correct at the time of drafting and wrong at the time of sending.
Three mechanisms reduce this risk. First, timestamp: attach to each draft the state of the thread used to write it (number of messages, identifier of the last message processed). Second, invalidate: on every new message in the thread, mark the draft "needs review" rather than leaving it as is. Third, display: during review, show what has changed since drafting, not just the draft text.
For a late reply — you reply three days later — the same principle applies in reverse: re-read the whole thread, not just your preserved draft. The question to ask is not "what did I want to say?" but "what has happened since?". This systematic review is part of the controls that an agent working on drafts must make visible rather than hide.
Webhook duplicates and deduplication
A webhook can be delivered twice. This is not an exceptional malfunction: notification systems guarantee "at least once", not "exactly once". If your automation triggers an action on every receipt, a duplicate webhook produces a duplicated action: two drafts, two follow-ups, two filings.
Deduplication relies on an identity key defined by you, not by the sender. A robust key typically combines: the identifier of the source event when it exists, otherwise a stable tuple (message identifier, action type, reduced timestamp). On every event, the system checks whether the key has already been processed; if so, it logs and ignores.
Watch out for two pitfalls. First, the overly broad key: deduplicating on the thread identifier alone would ignore legitimate subsequent messages. Second, the deduplication window that is too short: a duplicate can arrive several minutes after the original. This topic connects with the general management of retries and duplicates in workflows: idempotence, identity key and operation log form a triad to be designed together.
Lost supplier reply: review and proof
The most costly scenario is not the visible duplicate but the silent loss. Your tool sends (or prepares) a reply to the supplier; the operation partially fails; no draft exists, no send occurred, and nobody notices until the missed deadline.
Three distinct notions must be separated: the transport connection (the network and authentication worked), the supplier authorization (the account had the right to act) and the operation proof (you can demonstrate that the specific action was executed). A successful connection proves neither authorization nor execution. That is why every automated action must leave a verifiable trace: identifier of the created or modified object, timestamp, result of the call.
In practice, a useful proof contains at minimum: the deduplication key, the identifier of the created draft (not just the message), the result of the operation and the state of the thread at the time of the action. Without this trace, a forgotten supplier follow-up is only detected through the supplier's complaint.
Decision table: what to do depending on the situation
| Situation | Priority check | Recommended action |
|---|---|---|
| New message on an already-handled thread | Current state of the thread, not of the message alone | Re-evaluate the action, invalidate the existing draft |
| Two identical events received | Deduplication key | Log the duplicate, execute only once |
| Draft older than the last message | Gap between drafting and thread state | Mark "needs review", display the changes |
| Late reply to send | Full thread history | Re-read the entire thread before any send |
| Webhook received with no corresponding event | Authorization and origin | Log, do not act, alert if repeated |
| Operation reported successful but object not found | Operation proof vs transport | Replay only with an idempotent key |
| Doubt about a scope's coverage | Calls actually made | Restrict at application level, verify on output |
Weekly review procedure
A simple procedure, to run every week on a sample of automated threads:
- List the threads where the automation acted (draft created, filing, follow-up).
- For each thread, compare the expected state (log) and the actual state (Gmail): number of messages, drafts present, consistent identifiers.
- Check that no action duplicate appears for the same deduplication key.
- Check that no send occurred if your rule is "drafts only" — this is an output check, not an assumption.
- For each draft still pending, check whether it is newer than the last message in the thread; if not, invalidate it.
- Note the gaps and adjust the deduplication key or the triggers, not just the texts.
This procedure does not prove the absence of errors, but it detects the three most frequent drifts: duplicated drafts, replayed actions and obsolete replies. It lends itself well to conversational agent testing methods, where known scenarios are replayed rather than improvised.
If you would like to define an email automation mission with these controls from the design stage, you can describe your need on our offers page.
Frequently asked questions
Why not simply deduplicate on the thread identifier?
Because a legitimate thread produces several distinct events: every new message must be able to trigger a re-evaluation. The deduplication key must identify the event (message + action), not the conversation. Otherwise, you lose subsequent messages, which is worse than a visible duplicate.
Can the draft identifier change?
No: in Gmail, the draft identifier is stable, including after the content is modified. It is the identifier of the message contained in the draft that changes at each revision. So store the draft identifier as the durable reference and re-read the message identifier on every access.
Does the gmail.compose scope prevent sending?
No. The gmail.compose and gmail.modify scopes can also authorize sending messages. If your rule is "drafts only, never automatic sending", it must be enforced by application-level controls: an allowlist of authorized calls and an output check that no send occurred.
How to detect a stale draft without re-reading everything manually?
Attach to the draft the state of the thread at the time of drafting: identifier of the last message processed and number of messages. On every new message in the thread, compare. If the state has changed, mark the draft "needs review" and display the gap during human review.
Can a webhook delivered twice create two replies?
Yes, if your system acts on every receipt without an identity key. Notifications are guaranteed "at least once". Define a stable key (event identifier or message/action/timestamp tuple), check it before any action and log the ignored duplicates so you can audit.