"They never got the email" splits into three different problems with three different answers. Find out which one you have before doing anything else.
The three failure points
flowchart LR
A["Work queued"] --> B["Handler runs"]
B --> C["Send accepted<br/>and logged"]
C --> D["Delivered to<br/>the inbox"]
A -.->|"never drained"| X1["Async work"]
B -.->|"handler failed"| X1
C -.->|"no log row"| X2["Never sent"]
D -.->|"log row exists"| X3["Delivery problem"]
style A fill:#e8f4f9,stroke:#00415a,stroke-width:2px,color:#002230
style B fill:#e8f4f9,stroke:#00415a,stroke-width:2px,color:#002230
style C fill:#f5f3ff,stroke:#7c3aed,stroke-width:2px,color:#2e1065
style D fill:#eef2ff,stroke:#4f46e5,stroke-width:2px,color:#1e1b4b
style X1 fill:#fff1f2,stroke:#be123c,color:#4c0519
style X2 fill:#fff1f2,stroke:#be123c,color:#4c0519
style X3 fill:#fff1f2,stroke:#be123c,color:#4c0519
Start with the email log. Whether a row exists tells you which half of the problem you are in, and that is the only question worth answering first.
| Log row | Meaning | Next step |
|---|---|---|
| No row | Commerce never sent it | It is queued, retrying, or the handler is failing → Async work |
| Row exists | Commerce sent it and the provider accepted it | The problem is delivery, not the platform — see below |
What the email log records
Searchable from the back office, with a detail view per entry.
| Field | What it gives you |
|---|---|
| Template key | Which email this was — order acknowledgement, invitation, and so on |
| Related entity type and id | What it was about, for example an order id. This is how you find the email for a given order |
| Recipient | The address it actually went to. Check this against the address the customer expects |
| Subject | As rendered |
| Sent at | When it was accepted by the provider |
| Provider message id | The provider's own identifier — quote this when escalating to the mail provider |
The related entity id is the field to search on. Working back from an order number to its
acknowledgement is the common case.
When a row exists but nothing arrived
Commerce's part succeeded. The provider accepted the message and gave back an identifier, and that is the end of what this platform can see.
Delivery after that point is outside Commerce entirely, and the usual causes are:
- The recipient address is wrong — check the log's
recipientagainst what the customer expects, and against the customer record - The message was filtered as spam or quarantined
- The recipient domain rejected or deferred it
Take the provider message id to the mail provider. Commerce holds no delivery status, no bounce record and no open tracking. If a bounce came back, it came back to the provider, not here.
Note
There is no resend button. The log is a record of what was sent, not a console for sending. If a message genuinely needs to go again, the business event behind it has to be raised again.
Checking what an email would look like
The template screens can render a template against sample data without sending anything. Two uses worth knowing:
- Confirming a fix. After a template change, preview it rather than placing a test order.
- Answering "what did it say?". The log stores the subject but not the rendered body. Previewing the template shows the shape of what was sent, though not that specific recipient's data.
Templates and their layouts are stored in the database and editable live — a wording change is a back-office edit, not a deployment.
Where email is sent from
Delivery goes through Azure Communication Services, authenticated by the application's managed identity. There is no SMTP credential to rotate and no third-party mail account.
Practically: if sending is failing for every recipient rather than one, suspect the platform identity or the service itself rather than the templates — and the handler's error in the queue will say so.