Every broker worth using delivers at least once, so a handler will see the same message twice — on a redelivery after a crash, or when an acknowledgement is lost.
public function handle(SendReceipt $message): void
{
// the unique index arbitrates. a SELECT does not.
try {
DB::table('receipts_sent')->insert(['order_id' => $message->orderId]);
} catch (QueryException $e) {
if ($this->isDuplicateKey($e)) {
return; // already done
}
throw $e;
}
Mail::to($this->emailFor($message->orderId))->send(new Receipt());
}
Recording before the side effect rather than after is the ordering that matters: a receipt recorded after a successful send is not recorded when the process dies between the two. Recording first means a crash after the insert loses one email, which is the better failure. There is no ordering that gets both, which is the honest summary of exactly-once delivery.