Flow Transaction Finalizer Patterns
Purpose
Salesforce transactions are atomic: either every DML commits or none does. But some work should only run AFTER commit — sending an email, firing a webhook, writing an audit log to an external system, or issuing a compensating action if a callout later fails. Getting this wrong produces two common failures: "email sent but record rolled back," or "record saved but downstream never notified." This skill codifies the patterns Flow designers should use for reliable post-transaction behavior, and when to escalate to Apex Queueable with a Finalizer.
Recommended Workflow
- Classify the step. Pre-commit (validation), in-transaction (DML), or post-commit (notify / callout / external effect)?
- Separate two guarantees that sound alike. Post-commit execution — the work runs only if the transaction committed — is available three ways in Flow. Post-outcome execution — a callback that runs whether the work succeeded or threw — is not available in Flow at all.
- For post-commit work, default to the Run Asynchronously path. It runs after the original transaction for the triggering record is successfully committed, in its own transaction, and it is what makes a callout legal. It is available only on after-save record-triggered flows, and you cannot give it a time.
- Use Platform Events when the consumers should be decoupled, not merely deferred — three independent subscribers where adding a fourth should not touch the publisher. Confirm the definition is set to Publish After Commit.
- Cross into Apex when the work must report its own outcome or retry with a
bound. A Queueable's
System.FinalizerreadsParentJobResult.SUCCESSorUNHANDLED_EXCEPTIONand can re-enqueue a failed job up to five times. - Design idempotency at the boundary. Every mechanism here can run twice.
The key must be deterministic and derived from the record — never
$Flow.InterviewGuid, which differs on exactly the run you are guarding against. - Log every run and every give-up. Nobody is watching post-commit work. A silent stop at the platform's retry ceiling is indistinguishable from success.
Available Mechanisms
| Mechanism | Runs | Survives rollback | Callouts | Reports its own outcome |
|---|---|---|---|---|
| Pre-commit Flow step | Same txn | No | No | No |
| After-save flow, immediate path | Same txn, before commit | No | No | No |
| Run Asynchronously path | New txn, after commit | Yes | Yes | No |
| Platform Event (Publish After Commit) → subscriber | New txn | Yes | Yes, in the subscriber | No |
Queueable + System.Finalizer |
Async, after commit | Yes | Yes | Yes |
The last column is the whole decision. Everything above the bottom row defers work; only the bottom row tells you what happened to it.
Patterns
Pattern A: Run Asynchronously Path
The default for "must not fire on rollback" and for any callout. Open the after-save record-triggered flow → Start element → include a Run Asynchronously path. The path runs after the original transaction for the triggering record commits, so the external system is never told about a record that rolled back. Add a fault connector to a log record — it is the only evidence the path ran.
Subject to the asynchronous per-transaction Apex limits (200 SOQL, 60,000 ms CPU, 12 MB heap): higher than synchronous, not absent. Queued entries are visible on the Time-Based Workflow page in Setup.
Pattern B: Platform Event For Fan-Out
Choose this over three asynchronous paths when the consumers are genuinely
independent — the benefit is that adding a fourth subscriber does not touch the
publisher, not extra durability. Verify Publish Behavior on the event definition:
Publish Immediately fires even on rollback, and it draws on a different governor
allocation. See flow/flow-and-platform-events.
Pattern C: Apex Queueable + Finalizer
Reach for this when the requirement is to know whether the work succeeded and act on the answer. Flow calls an Invocable Action that enqueues a Queueable; the Queueable attaches a finalizer that writes the outcome and, on failure, decides whether to retry.
The ceilings that shape the design: one finalizer per Queueable job; a failed job can be re-enqueued five times and the count resets on success; the finalizer may enqueue a single asynchronous job; callouts are allowed. Re-enqueue replays the whole Queueable body, so everything before the failure point runs again.
templates/apex/ has no canonical QueueableWithFinalizer today — the
illustrative shape is in references/examples.md, and this is a flagged template
gap rather than an oversight.
Durability Cheatsheet
- "After commit, one consumer, possibly a callout" → Run Asynchronously path.
- "After commit, several independent consumers" → Platform Event, Publish After Commit.
- "After commit, and I need to record whether it worked and retry with a bound" → Queueable + Finalizer.
- "The user must not be able to save without this" → not a post-commit problem. Block the save with a Custom Error element.
Anti-Patterns (see references/llm-anti-patterns.md)
- The zero-minute scheduled path standing in for the asynchronous path.
- A callout on the immediate path of an after-save flow.
- Describing a fault path as "the flow's finalizer".
- Assuming a finalizer retries indefinitely.
$Flow.InterviewGuidas an idempotency key.- Using the fault path as a compensating transaction.
- "Async means no governor limits."
Related
flow/flow-transactional-boundaries— what commits when.flow/flow-and-platform-events— publish and subscribe mechanics.flow/flow-interview-debugging— instrumenting work nobody watches.flow/flow-batch-processing-alternatives— when the async ceilings also fail.apex/apex-transaction-finalizers— the Apex side in full.standards/decision-trees/async-selection.md— the formal engine choice. Cite the branch that resolved it rather than re-deriving it.
Official Sources Used
- Transaction Finalizers — https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_transaction_finalizers.htm
- Connect a Record-Triggered Flow to an External System Using an Asynchronous Path — https://help.salesforce.com/s/articleView?id=release-notes.rn_automate_flow_builder_asynchronous_path.htm&release=234&type=5
- Scheduled Paths — https://help.salesforce.com/s/articleView?id=platform.flow_concepts_trigger_scheduled_path.htm&type=5
- Publish Platform Event Messages Using Apex — https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_publish_apex.htm
- Per-Transaction Apex Governor Limits — https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm
The full annotated list is in references/well-architected.md.