Skip to content
Centriu
Centriu Vesper

Notification Dedup Key Mismatch Email Storm Automation: A Suppression List That Could Never Match

Centriu Vesper's task-execution engine runs a scheduled sweep — up to six distinct notification moments per task across its lifecycle, from a day-before reminder through three escalating overdue warnings — and is required to send each one exactly once, checking its own history log before creating a new one. That check depended on comparing two keys meant to identify "the same notification, for the same task, on the same day, for the same recipient": one key freshly built for each CANDIDATE notification about to be created, which included the recipient's own email address as its final component, and a second key rebuilt from the persisted, already-sent LOG that omitted the recipient's email entirely — one field shorter. Because a set built from the shorter format can never equal a lookup built from the longer one, the check `sentKeys.has(fullKey)` was structurally unable to ever return true for anything actually recorded in the persisted log, regardless of how many times a given notification had genuinely already been sent. The practical effect: every reminder, due-today notice and overdue warning the engine had EVER sent for a task still not marked complete was recreated as a fresh "needs sending" candidate on every subsequent scheduled run, indefinitely, for as long as the underlying task remained open — an email storm scaling directly with how long a task stayed unfinished. Fixed by building the persisted-log side of the comparison with the identical key shape used on the candidate side, so a genuine prior send is finally recognized as one.
Every send, resent every run
Same key shape, both sides
Person working on a laptop with notifications on screen
Sent once. Recognized as sent — finally.

A dedup check is only as good as both sides agreeing on what "the same" means

A deduplication check is, structurally, always a comparison between two things built by two different pieces of code at two different times: something computed fresh, right now, describing what's about to happen, and something reconstructed from a historical record describing what already did happen. The entire mechanism depends on both sides encoding the identical concept of sameness — the same fields, in the same order, formatted the same way — because a set membership check has no partial credit. A key that is one field short of matching isn't "almost the same notification" to a `Set.has()` call; it's simply a different string, indistinguishable from a notification that has never been sent at all.

How the underlying problem shows up before you fix it

A person with an outstanding task receives the identical reminder, due-today notice, or overdue warning email again on a later scheduled run, despite an entry already existing in the notification log recording that exact email as already sent.

The re-sending is not random or occasional — it is CONSISTENT and total: every notification type ever sent for a still-open task re-sends on every subsequent run, with no exceptions, because the suppression check can structurally never match ANY historical entry, not merely some of them.

The problem scales directly with how long a task stays open: a task closed the same day it was created generates the re-send only once or twice before completion removes it from consideration; a task left open for weeks accumulates a growing, repeating storm of duplicate emails across all six of its scheduled notification moments.

A within-the-same-run duplicate — for example, an admin who is also the task's assignee, who would otherwise receive the same notification twice in one execution — IS correctly suppressed, because that specific comparison happens entirely in memory during a single run, using the same key format on both sides. Only the comparison against the PERSISTED, cross-run log is affected.

The engine's own log of sent notifications keeps growing correctly and accurately with every genuine send — the defect is entirely in how that log is later READ back and compared against, not in how it is written.

Why a shorter key and a longer key can both look reasonable in isolation

The function building a notification's identity from a task, a notification type and a date is a genuinely reusable piece of logic — the SAME task, the SAME type, on the SAME day is a coherent, meaningful concept on its own, and reading just that function in isolation gives no indication anything is missing. The additional need to distinguish MULTIPLE recipients of the identical notification — an admin and the task's own assignee both receiving a due-today notice on the same day — was handled correctly at the point where new candidates are generated, by appending the recipient's email as a deliberate extra disambiguating field. That same appending step was never carried over to the code path reading the PERSISTED log back into a comparable set, because that second code path was written to reuse the base function directly rather than reproducing the exact same appending step done on the other side — two pieces of code, built to describe the identical concept, quietly diverging by exactly one field.

How Centriu Vesper found and closed a defect with no partial symptom to notice

Centriu Vesper's scheduled notification engine works from a fixed table of moments in a task's lifecycle — a reminder the day before it's due, a charge notice at two points on the due day itself, and three escalating overdue warnings on each day afterward — evaluating every open, assigned task against that table on each scheduled run and producing a list of notifications that should fire right now. Before creating each candidate, the engine is meant to check whether that exact notification has already been sent, by comparing a freshly-built key against a set of keys reconstructed from its own persisted sent-notification log.

The freshly-built key, computed for each candidate about to be created, concatenated the task's identifier, the notification type, and the date, then appended the specific recipient's own email address as an explicit final component — a deliberate design choice, since a due-today notice genuinely goes to both the assignee and every admin as two separate, individually-trackable sends. The set built from the PERSISTED log, however, was constructed by applying only the base task-type-date function to each historical log entry, with no equivalent appending step — one field shorter than what the candidate side was checking for. A `Set` built from shorter keys can never satisfy a membership check built against longer ones; the comparison `sentKeys.has(fullKey)` was, for every single historical entry, comparing a string against a different string, and returning false every time regardless of how genuinely accurate the underlying sent-log actually was.

The fix is narrow and exact: the persisted-log side of the comparison now applies the identical recipient-email-appending step used on the candidate side, reading the recipient's email directly from the log entry's own already-stored `recipientEmail` field — no schema change and no new data required, since the log had been correctly recording that field all along; only the code reading it back into a comparable key had been built inconsistently with the code writing the check against it. With both sides of the comparison built the same way, a genuinely already-sent notification is now correctly recognized on every subsequent run, and the corresponding email is correctly skipped.

What is actually built today

The persisted sent-notification log is read back into a comparable set using the exact same key shape — task, type, date, and recipient email — that new candidates are checked against, closing the one-field mismatch that made every historical entry structurally unmatchable.

A notification genuinely already sent to a specific recipient, for a specific task, type and day, is now correctly recognized as already sent on every subsequent scheduled run, and is not recreated as a fresh candidate.

The within-run duplicate suppression (the same notification going to two recipients, such as an assignee and an admin, in a single execution) — which was already working correctly — is completely unchanged by this fix.

No schema change or backfill was required: the recipient email needed for the corrected comparison was already being written to the log on every send; only the code reading it back was missing the equivalent step.

The six-moment notification schedule itself (day-before reminder, two due-day moments, three escalating overdue warnings) is unchanged — the fix corrects only whether an already-sent moment is correctly suppressed on a later run.

A task left open for a week (illustrative framing of the actual confirmed mechanism)

Before the fix, a task assigned to one person and left open past its due date would generate a due-today notice, correctly logged as sent. On the very next scheduled run, the engine would check its own log, fail to recognize that exact due-today notice as already sent because of the one-field key mismatch, and generate — and send — the identical notice again, logging a second, redundant entry. Every subsequent run would repeat the same pattern for every notification moment the task had already passed, compounding for as long as the task stayed open. After the fix, each of the task's six possible notification moments fires exactly once, correctly suppressed on every run after its first genuine send.

What changes operationally

A task's assignee and any relevant admins now receive each of Centriu Vesper's scheduled reminder, due-today and overdue notifications exactly once per genuine occurrence, rather than repeatedly on every scheduled run for as long as the task remains open — closing a gap that scaled email volume directly with how long a task stayed unfinished, independent of whether anything about the task had actually changed.

When this is not the right fit

This automation governs only the internal suppression logic deciding whether a given notification has already been sent — it does not change the notification schedule itself (which moments exist, at which hours, to which roles), and a workflow relying on receiving a reminder repeatedly on purpose would need a deliberately configured recurring notification, which is a different mechanism than this dedup check entirely.

Reusing a base key function vs. reproducing every disambiguating step consistently

Reusing a shared "base identity" function on both the candidate-generation side and the historical-log side looks, correctly, like the safer choice for keeping two comparisons in sync — shared code cannot drift the way two independently-written implementations can. The risk sits specifically in what happens AFTER that shared call: if one side appends an additional disambiguating field (here, the recipient's email, needed because a single notification type and day can genuinely go to more than one person) and the other side does not, the two paths silently stop describing the same concept, and no amount of the shared base function being correct changes that. The fix does not abandon the shared base function — it keeps it — but ensures the SAME additional step is applied consistently on both sides of the comparison, rather than only one.

Related systems

Main system: Centriu Vesper.

What it does NOT do

  • Does not change Centriu Vesper's six-moment notification schedule itself — the reminder, due-day, and overdue windows and their target roles are unchanged; this fix corrects only whether an already-sent moment is correctly recognized as sent on a later run.
  • Does not require any change to previously-logged notification records — the recipient email the corrected comparison needs was already being written on every send; only the code reading it back into a comparable key was fixed.
  • Does not affect the within-run duplicate suppression for two recipients of the same notification in a single execution — that comparison was already correct and is unchanged by this fix.
  • Does not retroactively identify or reverse any specific duplicate email already sent before this fix shipped — a team with that specific historical concern would need its own separate review of the affected period.
  • Does not change what a notification email says or looks like — the fix is scoped entirely to whether a notification is generated and sent at all, not to its content.

Security and governance

Centriu Vesper's notification engine now correctly recognizes an already-sent reminder, due-day notice or overdue warning against its own persisted log, closing a gap where every historical entry was structurally unmatchable and every notification for an open task re-sent on every scheduled run. Task, assignment and notification-recipient data referenced by this engine remain subject to Brazil's LGPD (Law No. 13,709/2018). Full detail on access control and audit trails lives at /governanca and /iso.

Pricing and contracting

Available by monthly subscription, with tiered plans. Values and terms come from the official pricing table at /precos (Centriu's central source — never restated here).

Frequently asked questions

Did this cause any task data or assignment to be lost or changed?

No — the defect was confined entirely to whether a notification email was correctly recognized as already sent. It never affected the underlying task, its assignment, its due date, or its completion status.

Why were some duplicates (within the same run) already correctly prevented, while others were not?

A duplicate within a single execution — the same notification going to two recipients in one run — was checked entirely in memory using consistent key building on both sides. The mismatch existed only in comparing a fresh candidate against the PERSISTED, cross-run log, which used a shorter key format.

Was this specific to one particular task, recipient, or notification type?

No — the key mismatch affected the comparison structurally, for every task, every recipient and every one of the six notification types, for as long as a task remained open past any of its scheduled moments.

Did fixing this require changing or backfilling any previously-logged data?

No — the recipient email needed for the corrected comparison had always been correctly written to the log on every send. Only the code reading that log back into a comparable set was missing the equivalent step, and that is the only part that changed.

Does the fix change how many notification moments a task can generate?

No — the six-moment schedule (a day-before reminder, two due-day moments, three escalating overdue warnings) is completely unchanged. The fix corrects only whether an already-fired moment is correctly suppressed on a later run.

What does Centriu Vesper cost?

It is sold by subscription with a published starting price — exact current values are on the central pricing page.

See how Centriu Vesper keeps task reminders accurate and non-repetitive

Reach our commercial team directly, or leave your details below — we'll follow up with guidance for your case.

Sources

  1. Centriu Vesper — public product page — Centriu, 2026-07-20 · link(primária)
  2. Centriu Vesper — public factsheet (API, JSON) — Centriu, 2026-07-21 · link
  3. Law No. 13,709/2018 — Brazil’s General Data Protection Law (LGPD) — Presidência da República (Brazil), 2018-08-14 · link

Last material update on .

By · AI-assisted production, with human review