How do you stop an AI workflow from sending the same email or creating the same job twice? Give each intended action a stable identifier, record its state durably, and use the destination service's idempotency support where available. If a timeout leaves the outcome uncertain, reconcile that action before trying again.
Imagine your inquiry workflow has prepared a helpful reply. A person approves it. The email service accepts the send request, but the connection drops before your automation receives the response. The workflow reports a failure. Someone clicks “retry.” The customer receives two identical replies.
The draft was fine. The handoff was not. Before adding more autonomy to a small-team workflow, decide what happens when the same instruction arrives again.
Why retries are a normal operating condition
Stripe's webhook documentation explicitly warns that an endpoint can receive the same event more than once. It also says event delivery order is not guaranteed. Its guidance includes tracking processed event IDs; where separate Event objects represent duplicates, the underlying object ID and event type can matter.
That is a concrete example from one platform, not a claim that every tool behaves identically. Check the delivery and retry contract of your own form provider, CRM, queue, and email service. Ask whether it retries automatically, whether a manual replay is possible, and how it identifies the original event.
An AI step does not remove those questions. It may add another source of repeated work: a model can propose the same tool action again after receiving an unclear result. Put duplicate protection in the action handler rather than leaving it to the wording of a prompt.
Identify the business action, not the latest attempt
Idempotency means a repeated request can preserve the intended effect instead of creating another one. AWS's Builders' Library explanation favors a caller-provided request identifier to express intent. It also explains why matching request contents alone can be misleading: two identical-looking requests may be intentional.
For an illustrative inquiry workflow, keep three identifiers distinct:
- Event ID: which form submission or provider event triggered the workflow.
- Action ID: which approved operation should happen, such as sending the first reply for that inquiry.
- Attempt ID: which execution tried to carry it out. A retry gets a new attempt record while retaining the same intended action.
Do not use the model's newly generated text as the action identity. Two drafts can differ slightly while still representing one reply. Conversely, the customer may legitimately need a second, revised message. That should be a deliberate new action with its own approval, not a disguised retry.
Keep a small action record outside the AI conversation
Our recommended starting record contains an action ID, the triggering event, the approved payload or its reference, the responsible owner, the current state, and any destination-side receipt. Keep only the data needed for operation and review; avoid copying customer records into unrestricted logs.
Use states that explain what is known:
- Prepared: the draft and intended recipient are ready for review.
- Approved: a person approved that specific action and content.
- In progress: one worker has durably claimed the action.
- Accepted: the destination confirmed acceptance and supplied a receipt where supported.
- Unknown: the request may have succeeded, but the workflow cannot establish the outcome.
Claim the action atomically before dispatch so two workers cannot both pick it up. A separate “look up the row, then insert” sequence can race when two deliveries arrive together. Ask your implementer how the database or workflow platform enforces uniqueness.
This record alone cannot make an external email send exactly once. There is still a gap between the destination accepting the request and your system saving the result. Treating that gap honestly is the most important part of the design.
Use the destination's contract, and respect its limits
Stripe documents idempotency keys for API requests, including API v1 returning the saved result for a repeated key and checking that reused parameters match. API v2 has different retry semantics, so verify the version as well as the endpoint. Its retention and endpoint rules are specific to Stripe. Do not assume a CRM or email provider supports the same behavior.
For each tool that changes something outside your workflow, ask:
- Does this exact operation accept an idempotency key or a unique external reference?
- How long is that protection retained?
- What happens if the same key is reused with changed content?
- Can we look up the original result after losing the response?
When the provider supports safe retries, reuse the original action key and approved payload according to its documented rules. When it does not, route an uncertain outcome to reconciliation. Check the provider's activity record or receipt before deciding whether another attempt is appropriate. If there is no reliable way to establish the result, leave the action marked unknown and let its owner decide how to follow up.
Also label the result accurately. An email service accepting a message is not evidence that the recipient received or read it. Record delivery events separately if the provider supplies them.
Test the awkward cases before enabling automatic sends
Use synthetic inquiries and test destinations. These are Opslumo's suggested acceptance checks, not results from a customer deployment:
- Replay one event. It should return the existing action or status rather than create another intended reply.
- Deliver the same event concurrently. Verify that only one worker claims the action.
- Lose the response after acceptance. Confirm that the next step is a supported same-key retry or reconciliation, not a fresh send.
- Change the approved payload. The old action key should not silently authorize different content. Require a new, explicit decision.
- Replay after the provider's retention window. Confirm the workflow still consults its own record and does not assume the provider remembers forever.
- Receive an older event late. Check current business state before applying a change that could undo newer work.
Keep a short record of the expected result and observed behavior for each test. A green “workflow completed” badge is insufficient if two messages reached the test inbox.
Start with one customer-facing action
Choose the first reply, job creation, or another clearly bounded operation. Name its owner, define the identity of the action, and map the uncertain-outcome path. Keep AI-generated drafts under review while that handoff is being tested.
Our CRM follow-up guide covers ownership and handoffs. Use the action-permission checklist to decide which tools may act, and the AI agent ROI guide to count review and exception handling in the business case.
If you need help mapping this for your tools, bring one event example, the destination's API documentation, and the current retry behavior to a workflow review. That is enough to make the first design discussion concrete.
Sources and scope
- Stripe: Receive Stripe events in your webhook endpoint. Duplicate events and event ordering.
- Stripe: Idempotent requests. Key reuse, request parameters, and provider-specific behavior.
- AWS Builders' Library: Making retries safe with idempotent APIs. Request identity, atomic handling, and expressing caller intent.
Official sources checked October 24, 2026. The inquiry example, suggested states, and test plan are Opslumo's recommendations. This article does not claim that all providers offer idempotency or that these checks establish exactly-once delivery.
