A timeout does not tell you whether an automated action happened. The receiving service may have completed the work and lost the response on the way back. If your flow simply runs the same action again, it can send a second email, create a second record, or charge twice. Design for the unknown state before you turn retries on.

Give each intended action a stable identity. For a form submission, that might be an order ID; for a file process, a source ID plus a version; for a queue item, a durable work-item key. Store the planned action and key before the external call. When the service accepts the request, store the outcome against that same key. A later attempt should look up the record first and return the existing result rather than repeat the side effect.

Retry only failures that are plausibly temporary, such as a network interruption or a rate limit that includes a wait instruction. Bound attempts and increase the delay instead of hammering a struggling service. Microsoft’s Power Automate guidance recommends configurable retry policies and separate failure paths; the HTTP specification similarly distinguishes operations that can be repeated safely from those that cannot.

When the state is unknown, reconcile it. Query the downstream system by the stable identity, inspect the result, then decide whether a new action is necessary. Log the key, attempt number, response, and final disposition so a person can explain what happened. A “catch” branch that quietly repeats every write is not recovery.

This pattern costs a little more data modelling and monitoring. It is worth it wherever a duplicate has a customer, financial, or security consequence. Test the timeout path with harmless data before you trust the workflow with real work.

Name the intended effect

The safest retry design begins before the network call. Define the thing you intend to make happen: create one invoice for this order, send one notification for this incident, or publish one processed version of this source file. Give that intended effect a stable identifier generated from durable business data, not from the current attempt. An attempt number, timestamp, or random request ID can be useful for tracing, but it should not be the only identity if a retry must recognize earlier work.

Store an intent record before the side effect. At a minimum it should contain the stable key, the requested action, the target, the relevant input version, a status, and an owner or workflow run ID. The status can start as planned or pending, then move through sent, confirmed, failed, or unknown. Keep the record in a place that survives a worker restart. If the process dies after a successful downstream write, the next worker needs something more reliable than memory.

Be precise about what “duplicate” means. Two separate welcome emails might be harmless; two password-reset links can confuse a customer; two bank transfers may be unacceptable. A duplicate file can be fine when it is an immutable archive and damaging when a downstream system treats it as a new request. The consequence determines how much identity, checking, and human review the operation needs.

Classify failures before retrying

Not every error deserves another attempt. A temporary network interruption, a service-unavailable response, or a rate limit with a stated wait time may be retryable. An invalid recipient, rejected authorization, malformed payload, or failed validation usually needs correction, not repetition. Retrying the latter merely produces more noise and can hide the real problem behind a queue of automated attempts.

Write the categories down in the workflow rather than leaving them to a catch-all branch. For each category, state the action: retry after a bounded delay, reconcile first, route to a human, or stop permanently. Microsoft’s automation guidance recommends clear failure paths and configurable policies; that is useful because a default retry setting cannot know the meaning of your side effect. Make the policy observable in logs so an operator can see why a task waited or stopped.

Use bounded retries and increasing delays for transient failures. A tight loop can overwhelm a service that is already unavailable and make recovery harder. Respect a service’s documented retry instruction where it exists. After the budget is exhausted, put the work item in a visible failed or review state. Silent exhaustion is one of the worst outcomes: the system has neither completed the task nor told anyone that it needs help.

Reconcile an unknown outcome

Task token, completion ledger, and cable arranged as a recovery check

The difficult case is not a clear rejection; it is an unknown outcome. A request may leave your system, be accepted and completed downstream, and lose its response before you receive it. Do not issue a second write just because the first response is missing. Look for the effect using the stable key, a provider-supplied idempotency key, or an authoritative ledger. If the downstream API supports an idempotency mechanism, use it according to its documentation and preserve the same key across a retry.

The reconciliation result should be explicit. If the effect is present, mark the intent confirmed and save the external reference. If it is absent and the service can reliably say it was never accepted, a new attempt may be appropriate. If the answer remains uncertain, hold the work for review. That pause can feel inefficient, but it is more honest than pretending a timeout is proof of failure.

For systems without a useful lookup, change the design if duplication matters. Add a ledger you control, request a unique client reference in the external record, or put the consequential action behind a human confirmation step. There is no generic retry setting that can manufacture certainty when neither side can identify the intended effect.

Design the action boundary

Idempotency is a property of an operation in context, not a magic word on an HTTP request. RFC 9110 distinguishes methods whose intended effect can be repeated safely, but an application can still create duplicates if it maps a request to a fresh order each time. Conversely, a nominally non-idempotent create operation can be made retry-safe with an application-level key and a lookup rule. Look at what the service actually changes.

Keep preparation separate from the side effect where possible. Validate input, reserve the stable identity, and calculate the payload before calling the external system. After the call, persist the external result before triggering a dependent action. This reduces the number of places where an interruption can leave the workflow in a confusing half-state. It also gives an operator a smaller, clearer set of records to inspect.

When a workflow includes several effects, do not assume one retry policy covers all of them. Creating a draft, charging a card, and emailing a receipt have different recovery needs. Give each stage its own state and identity, and make the next stage depend on a confirmed prior state. A single “success” flag for the entire chain hides the very information needed to recover safely.

Make failures visible to people

Logs should answer an operator’s first questions without exposing unnecessary sensitive data. Record the intent key, attempt count, time, error class, target system, external reference when available, and final disposition. Link related events with the same key. Avoid logging raw credentials, whole payment details, or private message bodies simply for debugging convenience.

Build a modest review queue for items that are unknown or permanently failed. Include the proposed next action and the evidence already gathered. Give the reviewer a way to mark the operation confirmed, retry it with the same identity, cancel it, or correct the input. Without this queue, manual recovery often becomes a separate undocumented process—and that is where duplicates are likely to reappear.

Test the path intentionally. In a non-production environment, simulate a timeout after the downstream service accepts a request, a rate-limit response, a malformed input, a worker restart, and a duplicate delivery of the same queue message. Check that each produces the expected record and that only one side effect occurs. The most valuable test is a boring one: an operator can explain exactly what happened and return the system to a known state.

A practical implementation review

Before enabling retries, take one real action type and walk it through on paper. What key represents the intended effect? Where is that key stored before the call? Which errors retry, and after how long? How will the workflow find a previously completed action after a timeout? Who sees an item that remains unknown? If the answers require guesses, the retry policy is not ready.

Review the recovery record after an exercise as well. It should show a single intention, each attempt, the evidence used for reconciliation, and a final status that a human recognizes. Add monitoring for growing review queues, repeated failures, and unexpected duplicate-key conflicts. These are signals that a downstream contract or upstream input changed. The goal is not zero failures; it is a failure state that is bounded, visible, and recoverable.

Include the operational team in the exercise. They should be able to find one work item by its key, explain whether the external effect occurred, and take the documented next action without editing a database by hand. If recovery depends on a developer’s memory, it is not yet a reliable production process. Rehearsal turns a technical pattern into an owned operating procedure.

Keep the retry setting close to the operation it protects. A global policy that applies the same number of attempts to every connector obscures the actual risk. Give a payment, a notification, and a report export distinct rules, then review those rules whenever the downstream service changes its contract or error behavior.

Finally, measure the duplicate rate as well as the success rate. A workflow that appears healthy by its completion metric can still create expensive manual cleanup. The ledger and stable key make this measurement possible, and the measurement tells you whether the protection is working.

Keep an example of a resolved unknown state in the runbook. A concrete before-and-after record helps new operators recognize the difference between a retryable outage and an effect that already happened. It also makes post-incident review substantially less speculative.

Update that example when the service interface changes, so the runbook remains aligned with the actual evidence an operator can retrieve.

Limits

Connector behavior and idempotency support vary. This guide describes a design pattern, not a guarantee that a particular API will deduplicate requests. For financial, safety-critical, or regulated actions, follow the target service’s documented rules and involve the people responsible for the business process.