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

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

Send it

Node.js (payment webhook handler)
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.