Twice in One Morning: Why Your Scheduled Agent Sends the Same Report Twice
A client opens their inbox on Tuesday morning and finds the same weekly report twice. Same numbers, same subject line, minutes apart. Nobody on your team sent it twice on purpose. The workflow log shows green. And yet the client now has two copies, and the second one quietly tells them your operation does not keep track of its own work. Duplicate sends feel like a small embarrassment, but they are a trust problem. One duplicate report is easy to forgive. Two invoices, two confirmations, or two
A client opens their inbox on Tuesday morning and finds the same weekly report twice. Same numbers, same subject line, minutes apart. Nobody on your team sent it twice on purpose. The workflow log shows green. And yet the client now has two copies, and the second one quietly tells them your operation does not keep track of its own work.
Duplicate sends feel like a small embarrassment, but they are a trust problem. One duplicate report is easy to forgive. Two invoices, two confirmations, or two "final reminder" emails in a week make the recipient stop trusting every message after them.
The uncomfortable part is that nothing in the setup has to be broken for this to happen. A duplicate send is usually a memory problem wearing a scheduling costume. The agent did not forget how to send. It forgot that it already sent.
Three ways the same message goes out twice
The first way is the timeout lie. The agent calls the email service, the message goes out, and then the connection drops before the confirmation comes back. From the agent's side, the call failed. So it does what it was told to do on failure: it tries again. The second attempt is a brand-new send, not a continuation of the first, and the recipient gets two copies. Every retry policy in the world produces this failure unless something remembers the first send.
The second way is overlapping runs. A scheduled job that runs every fifteen minutes but takes twenty minutes to finish will eventually run twice at the same time. Both runs read the same "what is due" list, both see the same unsent items, and both send them. The schedule looks correct, the runs look healthy, and the overlap only shows up in the recipient's inbox.
The third way is the simplest and the most common: the agent never writes down that it sent anything. Each run rebuilds its send list from scratch by asking "what is due?" instead of "what already went out?" A run that crashes after sending but before finishing, a workflow that gets re-triggered by hand, or a second tool that sends the same report will all produce duplicates, because no record exists for the next run to check. The system has a log of what it attempted, but a log is not a memory. A log says what happened. Memory is what the next run reads before it acts.
All three failures share one root cause. The send is treated as a fire-and-forget action instead of a recorded fact. The agent's world resets every run, so "already sent" is not a state it can check.
What a send record has to hold
Stopping duplicates takes one small record, kept somewhere every run can reach, with four parts.
First, a stable key for the send: what was sent, to whom, and for which period. "Weekly report, client Acme, week of November 2" is a key. A random run id is not, because the next run generates a different one and the check never matches.
Second, a status: pending, sent, or failed. The status is what turns a retry from a guess into a decision. A pending send can be retried. A sent send must be skipped, and the skip should be logged as a success, not an error. A run that ends because it correctly skipped a duplicate did its job.
Third, a timestamp. Timestamps let the next run tell this week's report from last week's. A send record without a period eventually blocks work it should allow, like the same client legitimately receiving next week's report.
Fourth, the evidence: which run sent it and through which channel. When a client asks "did you send this twice," the answer should come from the record, not from searching three inboxes and a provider dashboard.
The order matters as much as the record. The agent should claim the key and write "pending" before it sends, check the record before it acts, and update the status after. A record written only after a successful send still leaves a gap: if the run crashes between the send and the write, the next run sees nothing and sends again.
The uncertain middle deserves a human, not a retry
There is one case no record fully solves on its own. The send goes out, but the run crashes before recording it, so the next run genuinely cannot tell whether the message went out. Blindly retrying risks a duplicate. Blindly skipping risks a message nobody received.
That case should go to review, not to either automatic answer. The next run should flag the uncertain send and let a person confirm against the provider's sent log. Uncertain outcomes are rare, and routing them to a person is cheaper than explaining a duplicate invoice to a client.
Give every run the same memory of what went out
This is the kind of failure a shared memory layer is built to prevent. Vilix AI gives scheduled agents one memory that every connected tool reads over MCP, instead of each tool keeping its own copy of what happened. When a report agent records a send, the reminder agent, the billing agent, and the next run of the same report agent all see the same record. A send recorded in one place is visible everywhere it matters, on the very next run.
Vilix AI is cloud-hosted, so there is nothing to install or maintain, and the same memory follows the agent across tools and devices. It stores full conversation history, not just facts, so the record of a send keeps its context: what was sent, why, and what happened around it. The free plan is free forever, with a 7-day Pro trial that needs no credit card, and everything can be exported or deleted anytime.
That shared record is what turns "did we already send this" from a question nobody can answer into a check every run performs. The agent wakes up, looks up the stable key, and either proceeds or skips with a logged reason. Overlaps stop mattering, because the second run finds the first run's record. Retries stop doubling sends, because a retry finds the original send's status.
A short checklist before the next scheduled send
Before the next run goes out, check four things. Does the run write a send record with a stable key before it acts, or only log after? Does it check that record before sending, and treat a skip as a success? Does it distinguish pending, sent, and failed, instead of treating every unknown as "send it"? And when the outcome is genuinely uncertain, does it flag the send for review instead of guessing?
A scheduled agent that wakes up blind will eventually send something twice. The fix is not a better schedule or a stricter retry limit. It is a memory of what already went out, shared across every run and every tool, checked before every send. The client should never be the one who notices the second copy.
Learn more about giving scheduled agents shared memory at Vilix AI.