# Shipment notification sent on state transition to shipped

- **Pattern:** `ab-001146` (`ecommerce-order-management.notifications.shipment-email`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001146
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001146)

## Why it matters

A shipment notification that omits tracking information is worse than no notification at all: it tells the customer the package has left the warehouse but gives them nothing to act on. Without tracking data in the email, the customer must contact support or log in to the account page to find the tracking number — and if they can't find it, the next call is to their credit card company. This is an iso-25010:2011 reliability gap: the email was sent but failed to deliver the information that makes it useful. The tracking number and carrier captured at the 'shipped' transition (see `tracking-info`) must flow directly into the email template.

## Severity rationale

High because a shipment email without tracking data gives customers no ability to monitor delivery, turning every 'where is my order?' contact into a preventable support ticket.

## Remediation

Wire the shipment notification into `lib/orders/shipping.ts` so the tracking data captured at ship time is passed directly to the email in `lib/email/order-emails.ts`.

```ts
// lib/email/order-emails.ts
export async function sendShipmentNotificationEmail(params: {
  to: string
  orderNumber: string
  trackingNumber: string
  carrier: string
  trackingUrl: string
}) {
  await emailService.send({
    to: params.to,
    subject: `Your order ${params.orderNumber} has shipped`,
    template: 'shipment-notification',
    data: params,
  })
}

// In markOrderShipped() (lib/orders/shipping.ts):
await sendShipmentNotificationEmail({
  to: order.user.email,
  orderNumber: order.id,
  trackingNumber,
  carrier,
  trackingUrl: `https://yoursite.com/orders/${order.id}/track`,
})
```

## Detection

- **ID:** `shipment-email`
- **Severity:** `high`
- **What to look for:** Find the code path that marks orders as shipped. This is typically an admin action or an API call from a fulfillment service. Count the email service calls in this path. Look for an email call that includes tracking information — enumerate which tracking fields are passed to the email template (at least 2 of: order number, tracking number, carrier name, tracking URL). Quote the exact email function name called. Check whether the tracking information captured during the shipped transition (see `tracking-info` check) is actually passed into the email or whether hardcoded placeholders are used.
- **Pass criteria:** When an order transitions to `shipped` status, at least 1 email notification is sent to the customer that includes at least 2 tracking data fields (tracking number and carrier, or tracking number and tracking URL). The email trigger exists alongside the status update in the same handler or is queued reliably. A shipment email that sends no tracking data (just "your order shipped") does not count as pass.
- **Fail criteria:** No email is sent when an order is marked shipped (0 email calls in the shipped handler). Or an email is sent but includes 0 tracking fields, making it an ineffective notification. Or the email service call uses a hardcoded placeholder tracking number rather than the actual tracking data.
- **Skip (N/A) when:** The project delegates shipment tracking and notifications entirely to a third-party shipping platform (e.g., ShipStation, EasyPost) that sends its own carrier notifications, and the project does not send a separate notification. No `shipped` status transition exists in application code.
- **Detail on fail:** `"The admin 'Mark as Shipped' handler at src/app/api/admin/orders/[id]/ship/route.ts updates order status and tracking number but makes 0 email calls. Customers have no way to know their order has shipped unless they check their account."`
- **Remediation:** Add shipment notification alongside the shipped transition in `lib/email/order-emails.ts` and wire it from the shipping handler:

  ```ts
  // lib/email/order-emails.ts
  export async function sendShipmentNotificationEmail(params: {
    to: string
    orderNumber: string
    trackingNumber: string
    carrier: string
    trackingUrl: string
  }) {
    await emailService.send({
      to: params.to,
      subject: `Your order ${params.orderNumber} has shipped`,
      template: 'shipment-notification',
      data: params,
    })
  }

  // In markOrderShipped() (lib/orders/shipping.ts):
  await sendShipmentNotificationEmail({
    to: order.user.email,
    orderNumber: order.id,
    trackingNumber,
    carrier,
    trackingUrl: `https://yoursite.com/orders/${order.id}/track`,
  })
  ```

## External references

- iso-25010 reliability

Taxons: user-experience

HTML version: https://auditbuffet.com/patterns/ab-001146
