A workflow is only partly specified when it describes the successful handoff. If a destination creates a task but its confirmation is lost, the sender cannot safely assume that nothing happened. Retrying with a new identity can create a second task. Marking the work complete without confirmation can hide a missing result.

This guide follows a fictional client-onboarding approval into a delivery task. Its downloadable PHP exercise uses two separate local SQLite databases to demonstrate the transaction and retry boundaries. It does not connect a CRM, send HTTP requests or run a background worker. For the business decision about which workflow to automate, start with the business process automation solutions guide.

Define three different states

The source application owns whether an onboarding request is approved. The receiving application owns whether a delivery task exists. The people doing that task own its eventual completion. An acknowledged handoff proves task creation under the example's contract; it does not mean onboarding work has finished.

The fictional action is request 42, workspace 7, revision 1. Its stable key is onboarding:7:42:1. A retry refers to that same approved action. It must not manufacture another key simply because the previous call failed. A changed request or a new approval needs a separate product rule, not an accidental reinterpretation of a pending delivery.

The exercise supplies synthetic user identities and a simple approver role. It checks that an approver belongs to the request's workspace before changing approval state. This demonstrates the stated local rule; it does not implement login, token verification, role administration or a complete authorization model.

Save the decision and the pending handoff together

Two separate writes create a failure window. If approval commits and the process stops before a message is recorded, an approved request can disappear from the delivery workload. Reversing the order can create work for an approval that later rolls back.

The transactional outbox pattern described by AWS stores the business change and a pending event in the same database transaction. A dispatcher later reads committed events and attempts delivery. In this exercise, the source transaction changes the request state and stores the stable key with its handoff payload. Failure before commit must roll back both changes.

That transaction stops at the source database. It does not also commit the receiving application's task. Recording an outbox entry makes the delivery intention recoverable; a functioning dispatcher, retry policy and exception owner are still needed to make progress.

Make the receiver recognize the same action

The receiver stores the task and its action identity together. A non-null unique key prevents another stored task with that identity. On a repeated key, the receiver compares the submitted payload with the stored one: matching work returns the original task identifier, while changed content produces a conflict instead of overwriting the earlier task.

SQLite's uniqueness rules treat nulls differently from ordinary equal values, so the example requires a non-null key explicitly. A unique database field also does not make a separate email or remote API call happen once. Any effect outside this receiver transaction needs its own delivery and recovery design.

For a real integration, read the provider's identity and retry contract. Stripe's webhook documentation, for example, describes retries and possible duplicate events. Its specific delivery rules do not define those of another API. A notification identifier, a customer identifier and the identity of one approved business action may serve different purposes.

Trace a lost confirmation

The local exercise separates receiver commit from sender confirmation
StepSource applicationReceiverMeaning
Before approvalRequest awaits an authorized decision; no handoff is queued.No delivery task.Submission alone must not start approved work.
Approval commitsApproved request and pending handoff exist together.No delivery task yet.The intended action is recorded with approval in the source transaction.
Receiver commits; confirmation is lostHandoff remains pending.One task exists for the stable key.The sender has an uncertain outcome, not proof of failure at the receiver.
Same handoff is retriedOriginal task identifier is recorded; handoff is acknowledged.The same task remains.Delivery has been reconciled without another business action.

The failure is injected after the receiver transaction commits and before the sender records the acknowledgement. This location matters: an exception before task creation would test a different problem. The two local databases have independent commit boundaries, and the calls between them model a handoff without introducing a network.

Run and inspect the bounded example

The complete workflow automation example contains the PHP source, requirements, command and recorded output. It uses synthetic records and two independent in-memory SQLite databases that disappear when the process exits. It demonstrates transaction behavior and retry logic within that process, not restart recovery or durable storage. No application or production data is required.

The acceptance checks inspect approval, pending handoffs and receiver tasks. They include denied approvals, a source rollback, repeat delivery and reuse of a key with different content. A later distinct request must create a distinct task. These are separate outcomes: a script exiting successfully would not by itself establish them.

We executed the complete exercise on PHP 8.3.6 with PDO SQLite and SQLite 3.45.1. It passed 42 assertions and ended with two tasks for two distinct approved requests. The recorded output was:

PASS: 42 assertions
Requests: one approved handoff retried safely, one rolled back, one later handoff delivered.
Receiver tasks: 2; simulated lost acknowledgment did not create a duplicate.
Scope: local sequential PHP/PDO SQLite; no HTTP, provider, real authentication, or concurrency test.

In an isolated copy, removing the receiver lookup and unique constraint made the unchanged checks fail on the lost-confirmation retry: FAIL: retry returns same task; expected 1, got 2. This demonstrates detection of that specific duplicate-processing fault. The download includes the exact change so the result can be reproduced.

The wider checks remain implementation work: actual provider authentication and signatures, concurrent workers, delivery ordering, rate limits, credential expiry, retry scheduling, process restarts, database durability and production load. The example's sequential local execution cannot establish those properties.

Assign recovery before enabling a live workflow

Make pending and rejected actions inspectable by the people responsible for them. Useful records include the source identity, action key, current delivery state, attempt details and confirmed destination identifier. Keep the operational record useful without copying credentials or unnecessary customer data into logs.

Classify failures by the selected API's actual contract. A transient connection failure may justify a bounded retry; invalid data or a revoked permission needs correction. An uncertain result needs reconciliation with the destination. None is solved by retrying indefinitely or clearing a failed row merely to make a dashboard look healthy.

For a Laravel implementation, dispatching after a database commit addresses jobs seeing uncommitted data. Treat that timing feature separately from the outbox pattern, which records the delivery intention with the business change. Whether business state and queued work share a transaction depends on the chosen driver, connection and implementation. The plain-PHP exercise is not a tested Laravel queue configuration.

Record the pilot's business owner, exception path and observed results in the business automation pilot brief. Our regression testing guide explains how a deliberately introduced fault can check whether assertions detect the behavior they are meant to protect. For a customer-record workflow, bring the scope and failure cases to a CRM development discussion.