Briefly: a platform event defined with publishBehavior set to PublishImmediately is delivered the moment you call EventBus.publish, and rolling the publishing transaction back does not recall it. Subscribers run. Records get written. The transaction that caused all of it never committed.
Salesforce documents this in the Platform Events Developer Guide, under “Decoupled Publishing and Subscription”. It is also the single most expensive thing to learn late, so rather than take the documentation’s word for it I built both variants of the same event in a dev org, subscribed a trigger to each, and made the publishing transaction fail on purpose to see which messages survived. One did.
The setup
Two platform events in my own Developer Edition org on API 67.0, Summer ’26, identical apart from one line of metadata.
<!-- TA_Immediate__e -->
<publishBehavior>PublishImmediately</publishBehavior>
<!-- TA_AfterCommit__e -->
<publishBehavior>PublishAfterCommit</publishBehavior>
Each has a single Note__c text field and an Apex trigger subscribing to it. The trigger does the simplest observable thing available, inserting an Account whose name records that the event arrived, because a subscriber that writes to the database in its own transaction is the clearest possible evidence that delivery happened.
trigger TA_ImmediateSub on TA_Immediate__e (after insert) {
List<Account> log = new List<Account>();
for (TA_Immediate__e ev : Trigger.new) {
log.add(new Account(Name = 'TA EVT TA_Immediate__e ' + ev.Note__c));
}
insert log;
}
The test, and what came back
One transaction. Set a savepoint, publish to both events, roll the whole thing back.
Savepoint sp = Database.setSavepoint();
Database.SaveResult r1 = EventBus.publish(new TA_Immediate__e(Note__c = 'rolledback'));
Database.SaveResult r2 = EventBus.publish(new TA_AfterCommit__e(Note__c = 'rolledback'));
Database.rollback(sp);
Both publish calls reported success before the rollback.
Immediate publish success=true id=e02xx0000000001AAA
AfterCommit publish success=true id=e01xx0000000001AAA
Then I queried the Accounts the subscriber triggers would have created.
NAME
TA EVT TA_Immediate__e rolledback
One row. The Publish Immediately event was delivered, its subscriber trigger ran, and it committed an Account in its own separate transaction. The Publish After Commit event left no trace at all, because the transaction that published it rolled back and took the event with it.
Why this is worse than it sounds
The dangerous shape is an event published early in a transaction that later fails on something entirely unrelated to the event itself, which in a mature org happens constantly. A validation rule fires. A callout times out. Your transaction rolls back and the database is left perfectly consistent.
The event is already gone. Whatever subscribed to it has already acted on a state of the world that no longer exists and never really did. If that subscriber is an integration pushing to a billing system, you have now told an external system about an order that was never saved.
There is no compensating message, because nothing knows the transaction failed. The rollback is invisible to the event bus.
The SaveResult tells you nothing useful
Look again at those IDs. e02xx0000000001AAA is not a record ID. It is a placeholder that EventBus.publish returns for every call, and the second event got the equally fictional e01xx0000000001AAA.
A true from isSuccess() means your event passed validation and was accepted for publishing. It does not mean delivery happened, it does not mean a subscriber ran, and the identifier it hands back is useless for correlating anything afterwards. Replay IDs are a subscriber-side concern.
Treat the SaveResult as a syntax check on your event, and nothing more.
Which behaviour to pick
Publish After Commit should be your default, and I would argue it should have been the platform default. It ties event delivery to the success of the work that produced it, which is almost always the semantics you actually wanted. The cost is latency, since nothing goes out until the transaction commits.
Publish Immediately earns its place in two cases I can defend. Logging and telemetry, where you specifically want the record of an attempt to outlive the failure of that attempt, is the obvious one. The other is a long transaction in which a subscriber needs to begin work before the commit lands and the coupling between the two is genuinely loose enough to tolerate a phantom message.
Everything else, and particularly anything that writes to an external system, wants After Commit.
The setting lives on the event definition rather than the publish call, so it applies to every publisher of that event across the org. You cannot vary it per call site. If you need both behaviours you need two events, which is exactly what I built here.
What I have not tested
I tested an explicit Database.rollback to a savepoint. An uncaught exception unwinding the transaction should behave identically, but I have not confirmed that and will not claim it. My subscriber was an Apex trigger rather than a Pub/Sub API client, so delivery semantics to an external consumer sitting outside the platform may differ in ways this particular test cannot show.
Frequently asked
Does a failed publish roll back? A publish that fails validation returns isSuccess() == false and no event goes out. The trap here is the opposite case, where the publish succeeded and the surrounding transaction did not.
Can I change publishBehavior on an existing event? It is a field on the event definition, so it can be edited, but changing it changes the behaviour for every publisher at once. Treat it as a breaking change to a contract rather than a setting.
Do published events count against DML limits? Each EventBus.publish call consumed one DML statement in my run. They are also subject to their own daily event publishing allocation, which is separate from the per-transaction limits.
What about Change Data Capture? CDC events are generated by the platform after the commit, so this particular trap does not apply. The comparison between the two is in platform events against CDC.