Start here. This is the direct spoken answer to practice first.
Why this question matters
This is a realistic backend design problem: the database update succeeds but the message publish fails, or the reverse. EF Core is usually sitting right at that boundary.
The EF-specific boundary is one SaveChangesAsync that writes both the business entities and the outbox row. EF Core already wraps that save in a transaction, so I do not add a manual transaction merely because an outbox exists. A separate dispatcher reads committed outbox rows and publishes them after the request transaction has completed; broker I/O never sits inside the database commit.