order confirmation email
Order confirmation emails: what to include and how to send them
What an order confirmation email must contain, how to send it reliably from your checkout with an email API, and how to avoid duplicate receipts.
In short
Send the order confirmation from your payment webhook or order job - not the checkout page - with an Idempotency-Key set to the order id, so retries and duplicate payment events never send two receipts. Include the order number, items, totals, delivery details and a support contact, and tag the message with the order id so you can find it in your logs.What to include
- Order number and date, in the subject and at the top.
- Each item with quantity and price, then subtotal, tax, shipping and total.
- Delivery address and estimated delivery, or how the customer will be notified.
- How to get help or change the order, with a reply-to that reaches a person.
Best practices
Send from the payment event
Trigger it when the payment provider confirms payment, not when the thank-you page loads - pages reload, webhooks retry.
Idempotency by order id
Use an Idempotency-Key like order-10428-confirmation. A second webhook or a retried job gets the original message id back instead of sending again.
Set reply_to
Send from a no-reply-style address if you like, but set reply_to to your support inbox - customers reply to receipts.
Tag by order
tags: { order_id: "10428" } lets support find the exact email when a customer says it never arrived.
Plain text too
Include a text part with the order number and total; it helps deliverability and accessibility.
Subject lines that work
Order #10428 confirmedThanks for your order (#10428)Your Acme order #10428 is confirmed
Send it
async function onPaymentSucceeded(order) {
const res = await fetch("https://api.sendpository.com/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SENDPOSITORY_API_KEY}`,
"Content-Type": "application/json",
// Duplicate payment events can't send two receipts.
"Idempotency-Key": `order-${order.id}-confirmation`,
},
body: JSON.stringify({
from: "Acme Store <orders@mail.yourdomain.com>",
to: [order.email],
reply_to: ["support@yourdomain.com"],
subject: `Order #${order.number} confirmed`,
html: renderOrderEmail(order),
text: `Order #${order.number} - total ${order.total}`,
tags: { type: "order_confirmation", order_id: String(order.id) },
}),
});
if (!res.ok) throw new Error((await res.json()).error.type); // let the job retry
}Using another language? The same request works from any stack - it is JSON over HTTPS.
Questions
When should an order confirmation email be sent?
As soon as payment is confirmed - within a minute. Customers who don't get one quickly contact support or try to pay again.
How do I stop duplicate order confirmation emails?
Send with an Idempotency-Key derived from the order id. Payment providers retry webhooks, and the key makes the second attempt return the first message instead of sending another.
Is an order confirmation a transactional email?
Yes. It is triggered by the customer's own action and carries information they need, so it doesn't need marketing consent - but keep promotions out of it to stay that way.
Related
Send your first email in minutes
6,000 emails a month (up to 200 a day), free forever. No card needed.