Skip to content

The Return Receipt

Module 5 of 9 in the chapter Reliability, Recovery, and Observability, chapter 14 of the Integration Architect course. Each module stands on its own. Previous: Retries Without Duplicates · Next: Escalation and Recovery

The question this module answers: How does the sender know a message actually arrived, not just that it was sent?

When you send something important by post, you don’t just drop it in the box. You send it registered, with a return receipt. You keep your copy, and a card comes back, signed by the person who received it. If the card doesn’t come back, you know to follow up.

That’s the design I’ve come to trust most for proving delivery, and I’ve compared it with other architects who arrived at the same place. When a delivery’s status changes, Salesforce writes a small local notification record and publishes a platform event. The integration layer picks up the event and delivers it to the partner. And once it’s delivered, the integration layer calls back into Salesforce and marks the notification delivered. That’s the card coming back. A small batch job looks for notifications with no receipt after, say, three hours, and sends them again.

The Data Architect course walks through it on exactly this arrow, Steve Trucking’s status out to Redrock, in three drawings: send, sign, sweep (Proof of Delivery). Go there for the walk-through. This module is what an integration architect adds: the name the industry gives it, why the record must be saved with the change, what “sent” really means, and when an arrow needs a receipt at all.

The event bus is a good road. It keeps events for 72 hours, and a subscriber that drops off asks for everything after the last one it saw (chapter 11 module 4). So why build anything more?

Because Salesforce is honest about the fine print. Publishing is asynchronous, and “In rare cases, the event message might not be persisted in the distributed system during the initial or subsequent attempts. This means that the events aren’t delivered to subscribers, and they aren’t recoverable.” And publishing “uses the at-least-once pub/sub model, not the exactly-once model”, so once in a while an event arrives twice.

Rare isn’t never. And past the 72 hours, a subscriber that was down too long can’t replay what it missed. If the business has to know every status reached Redrock, the event bus alone can’t tell you. You need your own record of what you sent, and someone to sign for it.

Notice something else in that design. Publishing an event isn’t a callout. Salesforce doesn’t have to know Redrock’s address, its credentials, or how to retry. The integration layer does the calling. But I still track it locally, on the Salesforce side, because Salesforce is the one that needs to know.

This isn’t only my design. The industry has a name for its first half: the transactional outbox. Chris Richardson’s pattern catalog puts it in two sentences: “The solution is for the service that sends the message to first store the message in the database as part of the transaction that updates the business entities. A separate process then sends the messages to the message broker.”

The key words are as part of the transaction. The notification record has to be saved in the same transaction as the change it reports. Then the two can’t disagree. If the status change commits, the notification commits with it. If the save fails and rolls back, the notification rolls back too. The pattern’s own claim: “Messages are guaranteed to be sent if and only if the database transaction commits.”

Compare that with publishing on its own. Choose Publish Immediately, and the event goes out “regardless of whether the transaction succeeds”, so Redrock can hear about a delivery that was never saved. Choose Publish After Commit, and the event waits for the save, but if that one event is among the rare few that isn’t stored, nothing on your side remembers it existed. The outbox closes both gaps: the record is the memory, and it lives and dies with the change.

The outbox: saved with the change The outbox: saved with the change. Two panels. Publish only: the status change and the event are separate; with Publish Immediately, the event can go out even when the save fails; with Publish After Commit, the event waits for the save, but if it's one of the rare events not stored, nothing remembers it; either way, Salesforce can't say what reached Redrock. With the outbox: the status change and the notification record are saved in one transaction; both commit, or neither does; the event is published, and the sweep sends again anything without a receipt; the notification is the memory of what was sent. Underneath: save the record of what you'll send in the same transaction as the change it reports. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.58.0", "built": "2026-11-04", "id": "DGM-INT-FM-13", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "comparison/side-by-side 0.5.0", "theme": "stevetecharc 1.4.0"} Publish only the change and the event apart Publish Immediately: it can go when the save fails. Publish After Commit: it waits for the save. A rare event that isn't stored: nothing remembers it. Salesforce can't say what reached Redrock. With the outbox the change and the notification together The status change and the notification, in one transaction. Both are saved, or neither is. The sweep sends again what has no receipt. The notification is the memory of what was sent. Save the record of what you'll send in the same transaction as the change it reports. © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

Saved with the change, or not at all.

The outbox makes sure the message gets sent. The second half of my design, the receipt, makes sure it arrived. And the third piece is on the far side: because the sweep can send a status again, the receiver has to be safe to receive it twice. Enterprise Integration Patterns calls that an idempotent receiver: “one that can safely receive the same message multiple times.” Module 4 showed how: the ID that travels with the status, and the time of the change for the order (module 4).

Outbox, receipt, idempotent receiver. Three names for the three parts of registered mail: your copy, the signed card, and the clerk who doesn’t stamp the same letter twice.

Here’s a question I like to ask: when Salesforce says an event was “sent,” what does it actually know? There are three different answers, and they’re easy to mix up.

Queued. When Apex publishes, the immediate answer only says the request was accepted for publishing. Salesforce’s guide: “Without the callback, you can get only the intermediate queueing result in Database.SaveResult of an EventBus.publish call, not the final result.” Chapter 11 called this “published means queued” (chapter 11 module 3).

Published. For the final answer from the bus, Salesforce gives you Apex publish callbacks: a class implementing EventBus.EventPublishSuccessCallback or EventBus.EventPublishFailureCallback, called when the publish finally succeeds or fails. The event’s EventUuid “is used to match the events returned in the callback result with the events in the publish call.” So a failed publish can mark its notification failed, or publish it again, at once, instead of waiting for the sweep. They’re available for high-volume platform events.

Delivered. Neither of those tells you Redrock got it. A successful publish means the bus has the event. Only the receipt, the integration layer’s call back after Redrock accepted the status, means it was delivered.

Three kinds of sent Three kinds of sent, in three numbered steps. 1, queued: EventBus.publish returns a SaveResult; Salesforce accepted the request; that's all it knows yet. 2, published: an Apex publish callback brings the final result; the event is on the bus, or it failed, and the failure can be handled at once. 3, highlighted, delivered: the integration layer calls back and marks the notification delivered; only the receipt says Redrock has it. Underneath: published isn't delivered; only the receipt proves the other side has it. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.58.0", "built": "2026-11-04", "id": "DGM-INT-FM-12", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "flow/sequence 0.1.0", "theme": "stevetecharc 1.4.0"} Queued EventBus.publish returns a SaveResult The request was accepted. That's all, so far. Published an Apex publish callback, the final result The event is on the bus, or it failed, and that can be handled at once. Delivered the integration layer marks the notification delivered Only the receipt says Redrock has it. 1 2 3 WHAT SALESFORCE KNOWS Published isn't delivered. Only the receipt proves the other side has it. HOW TO READ IT 1 the order a step the step that matters most what comes next © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

Published isn’t delivered.

So when someone in a design review says “we know it was sent,” ask them which of the three they mean.

Test knowledge. Know that platform event publishing is asynchronous and at least once: the SaveResult of EventBus.publish is only the queueing result, Apex publish callbacks give the final result (high-volume events), and in rare cases an event isn’t persisted and “aren’t recoverable.” Know that proof of delivery needs your own tracking: a record of what was sent, saved with the change, and an acknowledgment back.

Once you start looking for return receipts, you find them all over integration. Some protocols were built with one.

Files. A file written to a partner’s folder isn’t delivered. It’s just there. For Steve Trucking’s nightly status file to Keystone, the contract says plainly: “a file isn’t proof; Keystone’s pickup or acknowledgment is, and a missing one is chased by morning” (chapter 10 module 6). The pickup is the receipt.

EDI. Business-to-business messaging has had receipts for decades. For example, AS2, the standard for sending EDI and other business documents “using the HTTP transfer protocol”, builds in “non-repudiation of receipt”, with a “Signed Receipt: A receipt with a digital signature” coming back to the sender (IETF RFC 4130, 2005). If a partner sends you EDI that way, the receipt is part of the standard. Your job is to make sure someone checks that the receipts came back.

Webhooks. A partner’s 200 answer to your webhook call is a receipt of sorts: their server accepted it. That’s why, in my design, the integration layer only marks the notification delivered after Redrock answers.

So the question isn’t always “should we build a receipt?” Sometimes it’s “this protocol has one already; who’s watching for the missing ones?”

No. A return receipt costs something: a record per message, a callback, a sweep job, a purge. Build it where the business has to prove delivery.

I’d ask three questions of each arrow:

  • Would a lost message cost money, a promise, or a legal duty? A delivery status a partner bills from, a payment, a signed proof of delivery: yes.
  • Would anyone notice, and how soon? If Redrock’s customers would phone about a missing status, you want to know before they do.
  • Does the next message replace it anyway? FleetLink’s truck position is out of date in minutes. A lost one is replaced by the next. No receipt needed, just as module 1 found it needed no retry.

And remember the notifications are a log like any other. Purge them in tiers, as in module 3: the delivered ones after a few days, the rest once they’re resolved.

One more thing the receipts give you for free: a count. Statuses published today, receipts back, how many sent again. That’s a completeness check you can report on every morning (module 8).

Beyond the exam: what AI changes. An AI assistant will happily build “reliable delivery” when you ask for it, usually as retries around a publish call. That’s the queued and published kinds of sent. Ask it the third question too: how will we know the other side has it? If its answer doesn’t include a record saved with the change and a receipt coming back, it built a hope, not a proof.

  • Which arrows need proof of delivery, by what a lost message would cost, and which don’t.
  • The outbox: the notification record saved in the same transaction as the change it reports.
  • Who signs the receipt, and when: after the far end accepts, not when the middle receives.
  • The sweep: how long to wait for a receipt, how often to send again, and who’s told when it keeps failing.
  • The receiver, safe to receive the same message twice.
  • Receipts the protocol already has, and who watches for the missing ones.