Platform Events, Event-Driven Architecture & Reliability
A Platform Event is not a guaranteed command bus
Decoupling does not remove responsibility
An Opportunity closes and Salesforce publishes an Order Requested event. The ERP subscriber is offline, a second consumer sends a confirmation email and a third updates analytics. The event publish call returned success, so the sales team assumes the order exists. Hours later the ERP has no order, the customer has an email and nobody can tell whether the message was never persisted, was delivered twice or failed inside the subscriber.
The JSBC Labs position is that a Platform Event is a transport mechanism, not a guaranteed command bus. It can create excellent asynchronous boundaries, but it does not by itself provide end-to-end completion, exactly-once processing or permanent history. Producers, subscribers and operators still need a shared business contract, durable identifiers, idempotent effects, acknowledgements where outcomes matter and a recovery path when the stream cannot supply the missing message.
Publish a business fact—or define a command protocol
Events work best when they describe something that happened: Credit Assessment Completed, Candidate Consent Withdrawn or Shipment Dispatched. Several consumers can react without the producer knowing their implementations. An imperative name such as Create Invoice is different. It targets an owner, expects a result and usually carries deadlines, validation rules and failure semantics. Putting that phrase on an event does not make those responsibilities disappear.
Commands can travel asynchronously, but design them as a protocol. Include a command ID, target capability, requested timestamp, schema version and correlation ID. Define accepted, rejected, completed and timed-out outcomes, plus who reconciles commands that receive none. If the user requires an immediate answer, Salesforce architecture guidance says event-driven processing is the wrong interaction pattern; use a synchronous boundary and publish facts after the transaction completes.
Choose the transaction boundary deliberately
Salesforce Platform Events support Publish Immediately and Publish After Commit behaviour. Immediate publication does not respect the surrounding transaction boundary, so a subscriber can receive an event even if the producer later rolls back. After Commit publishes only after a successful commit and is the safer default when consumers depend on committed record state. The choice is business semantics, not a performance toggle.
After Commit still does not turn the event bus into the Salesforce database. Salesforce documents high-volume publication as asynchronous: a successful immediate SaveResult means the request was queued, not that every subscriber received it. In rare cases an event can fail to persist and be unrecoverable. For financially or operationally critical handoffs, JSBC Labs recommends pairing the business change with durable dispatch and reconciliation state so an operator can detect and repair a missing outcome.
Treat duplicates as normal input
Salesforce uses an at-least-once publication model and documents a small chance of duplicate events when internal retry follows an uncertain acknowledgement. A subscriber that performs an irreversible action on every delivery can therefore create duplicate payments, emails, cases or orders. The correct response is not to hope duplicates remain rare; it is to make repeated delivery harmless.
Give every business operation a stable idempotency key and persist the processed result at the same boundary as the side effect where possible. Salesforce exposes EventUuid to identify a platform event message, while a domain identifier should represent the business operation across republishing or channel changes. Check that key before acting, return the previous outcome for a duplicate and expire deduplication records only after the business risk window—not merely after event retention ends.
Do not mistake replay for a permanent queue
Salesforce retains high-volume Platform Events for 72 hours. Pub/Sub API clients can resume after a saved Replay ID while the event remains inside that window. Replay IDs are opaque, are not guaranteed to be contiguous and can become invalid after certain maintenance or org-migration events. They identify a stream position; they are not durable business keys and must never be calculated.
Persist the last successfully processed position only after the corresponding business work is durable. Decide what happens when a subscriber is down beyond retention, its checkpoint is invalid or the retained stream resets. Recovery may require a source-of-truth query, a reconciliation extract or controlled republishing from a durable ledger. EARLIEST can retrieve retained messages, but it cannot recreate events that have already been purged.
Ordering must be designed above the stream
Salesforce guarantees the order of events inside one publish request, but not across separate requests that may be handled by different application servers. Network retries and parallel consumers can further change the order in which business effects become visible. A subscriber that assumes Address Changed always arrives before Account Activated will eventually apply stale state or reject valid work.
Where sequence matters, include an aggregate ID and a monotonically increasing domain version created by the system of record. Reject or park older versions, detect gaps and fetch authoritative state when rebuilding from events is unsafe. Do not use Replay ID as the domain sequence. It belongs to delivery and can change independently of the customer, order or case whose lifecycle you are modelling.
Engineer subscribers for partial failure
Apex Platform Event triggers can receive large batches and share transaction limits across that execution. Process collections, isolate record-level decisions and avoid one poison event repeatedly blocking unrelated work. Salesforce provides setResumeCheckpoint for resuming after the last successfully processed message and EventBus.RetryableException for transient conditions that justify replaying a batch. Those mechanisms have different commit and retry semantics; choose them intentionally.
External subscribers need the same discipline: bounded retries with backoff, durable checkpoints, circuit breaking and a quarantine path containing the event ID, schema version, error and correlation context. Never retry a permanent validation failure forever. Make replay safe before increasing throughput or parallelism, because concurrency magnifies duplicates, reordering and contention that a single happy-path consumer concealed.
Operate the event flow as a business service
Monitor more than publish-call success. Track final publish failures where Apex callbacks are appropriate, subscriber connection state, checkpoint age, processing latency, duplicate rate, retry count, quarantined messages and the gap between requested and completed business outcomes. Salesforce exposes subscriber state for Apex and other platform subscribers; external consumers should emit equivalent telemetry linked by correlation ID.
Test the failure modes deliberately: roll back the producer, disconnect a subscriber, deliver the same event twice, reorder aggregate updates, exceed the replay window and inject a poison message into a mixed batch. Reconcile the resulting records against the system of record. A mature event architecture is not one where every producer can publish quickly. It is one where teams can prove what happened, resume safely and repair the business process when asynchronous delivery behaves exactly as documented.
Official references
- Salesforce Architects — Event-Driven Architecture Decision Guide
- Salesforce Developers — Platform Event Considerations
- Salesforce Developers — Platform Event Fields and Publish Behaviour
- Salesforce Developers — Event Message Durability
- Salesforce Developers — Subscribe RPC Method
- Salesforce Developers — Get the Result of Asynchronous Platform Event Publishing with Apex Publish Callbacks
- Salesforce Developers — Identify and Match Event Messages with EventUuid
- Salesforce Developers — Comparing setResumeCheckpoint and EventBus.RetryableException