# WooCommerce Post-Purchase Emails

> The window right after a WooCommerce purchase is the highest-engagement moment a customer ever has with you. A well-timed thank-you / usage-tips / review-request / cross-sell sequence quietly increases LTV without any additional ad spend. This guide walks the hook setup, the 4-step sequence template, the product-category tagging trick, and the AcelleMail automation that ties it all together.

- Canonical URL: https://acellesend.com/kb/articles/woocommerce-post-purchase-emails
- Author: Tom Becker, Developer Editor
- Category: Integrations — Stripe, PayPal, WordPress, WooCommerce, Zapier, webhooks, API
- Content type: tutorial · Difficulty: intermediate
- Tags: Acellemail
- Published: 2026-02-17 · Last updated: 2026-08-15
- Reading time: 8 min
- Publisher: AcelleMail Knowledge Base, https://acellesend.com/kb

---

## What this is for

The window right after a WooCommerce purchase is **the highest-engagement moment a customer ever has with you.** Welcome them well and they convert again at 3-5× the baseline rate. Ignore them and you've left LTV on the table.

This guide walks the production setup: WooCommerce hook → AcelleMail list → automation flow. Pattern 2 from the [WordPress + WooCommerce integration overview](https://acellesend.com/kb/articles/integrating-acellemail-with-wordpress-and-woocommerce).

## Prerequisites

- A working WordPress + WooCommerce store
- A working AcelleMail install with an [API token](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api#step-1--get-your-api-token) configured
- An AcelleMail list dedicated to post-purchase subscribers (separate from your general newsletter — different sequence, different opt-in semantics)
- An [automation flow](https://acellesend.com/kb/articles/post-purchase-follow-up-automation) set up in AcelleMail to consume the events (we'll cover the template below)

## Step 1 — The WooCommerce hook

The right hook for post-purchase is `woocommerce_order_status_completed` — fires after payment AND fulfilment. NOT `woocommerce_thankyou` (fires on the thank-you page render even before payment confirms) or `woocommerce_payment_complete` (fires on payment but before fulfilment, can trip on subscriptions).

In a mu-plugin (per [WordPress Subscriber Sync Step 2](https://acellesend.com/kb/articles/wordpress-subscriber-sync#step-2--create-a-must-use-plugin)):

```php
<?php
/**
 * Plugin Name: AcelleMail WooCommerce Post-Purchase
 * Description: Hook WooCommerce order completion to AcelleMail post-purchase list
 */

if (!defined('ACELLEMAIL_API_TOKEN') ||
    !defined('ACELLEMAIL_POST_PURCHASE_LIST_UID') ||
    !defined('ACELLEMAIL_BASE_URL')) {
    return;
}

add_action('woocommerce_order_status_completed', function ($order_id) {
    $order = wc_get_order($order_id);
    if (!$order) return;

    $email = $order->get_billing_email();
    if (!is_email($email)) return;

    // Build category tags for personalisation in the automation
    $tags = [];
    foreach ($order->get_items() as $item) {
        $cats = get_the_terms($item->get_product_id(), 'product_cat');
        if ($cats) foreach ($cats as $c) $tags[] = $c->slug;
    }
    $tags = array_unique($tags);

    // Pick the "headline" product (highest-value line item) for the email subject merge
    $headline_product = '';
    $top_subtotal = 0;
    foreach ($order->get_items() as $item) {
        if ($item->get_subtotal() > $top_subtotal) {
            $top_subtotal = $item->get_subtotal();
            $headline_product = $item->get_name();
        }
    }

    wp_remote_post(ACELLEMAIL_BASE_URL . '/api/v1/subscribers', [
        'headers' => [
            'Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN,
            'Accept'        => 'application/json',
            'Content-Type'  => 'application/json',
        ],
        'body' => wp_json_encode([
            'MAIL_LIST_UID'   => ACELLEMAIL_POST_PURCHASE_LIST_UID,
            'EMAIL'           => $email,
            'FIRST_NAME'      => $order->get_billing_first_name(),
            'LAST_NAME'       => $order->get_billing_last_name(),
            'ORDER_ID'        => $order_id,
            'ORDER_TOTAL'     => $order->get_total(),
            'ORDER_CURRENCY'  => $order->get_currency(),
            'HEADLINE_PRODUCT' => $headline_product,
            'PRODUCT_CATEGORIES' => implode(',', $tags),
            'PURCHASE_DATE'   => $order->get_date_completed() ? $order->get_date_completed()->format('Y-m-d') : date('Y-m-d'),
        ]),
        'timeout'  => 10,
        'blocking' => false,
    ]);
});
```

Add the custom fields to the AcelleMail list (**Lists → your post-purchase list → Fields → Add**) before pushing — the API won't reject unknown fields, but they're discarded.

## Step 2 — Set up the AcelleMail list

In **AcelleMail → Lists → New**:

- **Name:** Customers (Post-Purchase)
- **Default subject prefix:** `[Your Store]`
- **Custom fields:**
  - `ORDER_ID` (number)
  - `ORDER_TOTAL` (decimal)
  - `ORDER_CURRENCY` (text, 3 chars)
  - `HEADLINE_PRODUCT` (text, 200 chars)
  - `PRODUCT_CATEGORIES` (text, 500 chars; comma-separated)
  - `PURCHASE_DATE` (date)
- **Opt-in:** Single (purchase IS implicit opt-in for transactional follow-up)

The reason to use a separate list (not your general newsletter): different cadence, different unsubscribe behaviour, different compliance status. A newsletter unsubscribe shouldn't stop post-purchase emails about an active order.

## Step 3 — The 4-step automation sequence

In **AcelleMail → Automation → New Workflow** on this list:

| Email | Delay | Subject example | Goal |
|---|---|---|---|
| **1. Thank you + setup** | Immediately | `Welcome, {FIRST_NAME} — your {HEADLINE_PRODUCT} ships soon` | Confirm receipt; set delivery expectations; link to setup guides |
| **2. Usage tips** | Day 3 | `Getting the most from your {HEADLINE_PRODUCT}` | Drive activation; reduce returns; build product confidence |
| **3. Review request** | Day 7 | `How's your {HEADLINE_PRODUCT}? Quick favour?` | Collect reviews; social proof for future buyers |
| **4. Related products** | Day 14 | `You might also like these...` | Cross-sell; based on PRODUCT_CATEGORIES tag |

This is the **proven** 4-step template — see [Post-Purchase Follow-Up Automation](https://acellesend.com/kb/articles/post-purchase-follow-up-automation) for the full content templates.

### Personalising per product category

Inside the automation, branch on `PRODUCT_CATEGORIES`:

- "If `PRODUCT_CATEGORIES` contains `coffee`" → use coffee-specific templates (related products, brewing tips)
- "If `PRODUCT_CATEGORIES` contains `electronics`" → use electronics templates (compatibility tips, warranty registration)
- Otherwise → use generic templates

Acelle's automation builder supports conditional branching — see the [advanced triggers guide](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions).

### When the customer buys again

If the customer makes a *second* purchase before completing the 4-step sequence, they're re-added to the list (Acelle handles as upsert) and the automation may re-evaluate. To prevent customers from getting "How's your X?" review-request emails for a product they bought last week, add an automation step: "If purchases in last 7 days > 1, skip" or use a re-entry cooldown.

## Step 4 — Handle refunds / cancellations

When an order is refunded, you may want to pause the automation for that customer (the day-7 review email feels off if they just got a refund):

```php
add_action('woocommerce_order_status_refunded', function ($order_id) {
    $order = wc_get_order($order_id);
    if (!$order) return;

    // Remove from post-purchase list to stop the automation
    // First find subscriber by email:
    $find = wp_remote_get(
        ACELLEMAIL_BASE_URL . '/api/v1/subscribers/email/' . urlencode($order->get_billing_email()),
        ['headers' => ['Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN, 'Accept' => 'application/json']]
    );
    $sub = json_decode(wp_remote_retrieve_body($find), true);
    if (!$sub || empty($sub['id'])) return;

    // Tag them as refunded (the automation can branch on this tag)
    wp_remote_post(
        ACELLEMAIL_BASE_URL . '/api/v1/subscribers/' . $sub['id'] . '/add-tag',
        [
            'headers' => ['Authorization' => 'Bearer ' . ACELLEMAIL_API_TOKEN, 'Accept' => 'application/json', 'Content-Type' => 'application/json'],
            'body' => wp_json_encode(['tag' => 'refunded:' . $order_id]),
        ]
    );
});
```

Then in the automation: "If `tag = refunded`, exit flow".

## Common issues

| What you see | Likely cause | Fix |
|---|---|---|
| Sequence fires twice for the same order | Order moved through `completed` status twice (e.g. manual admin retrigger) | Check tag for `order-id:{order_id}`; skip if already tagged |
| Review emails sent to refunded customers | No refund hook configured | Add Step 4 refund handler above |
| Custom fields not appearing in email merges | Custom fields not added to the AcelleMail list, OR field names lowercase | Add UPPERCASE field names in AcelleMail Lists → Fields BEFORE pushing |
| Customer complains about getting Day 14 email after a 30-day window passed | Slow queue / paused worker | Verify supervisor + queue per [queue setup guide](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) |
| Hook doesn't fire on subscription renewals | WC Subscriptions uses different hooks | Add `add_action('woocommerce_subscription_renewal_payment_complete', ...)` for renewals |
| Tag with comma in name (`product_cat` slug has comma) | `implode(',', $tags)` produces ambiguous joining | Use `;` separator instead, or filter out commas in slugs first |
| Customer base going up but automation engagement drops | Including non-customer registrations in same automation | Verify the post-purchase hook posts to the dedicated list (not the general newsletter) |

## FAQ

**WooCommerce Subscriptions hooks?** `woocommerce_subscription_renewal_payment_complete` fires on each renewal — add the same body to push to a subscriber-renewals list. Different sequence (skip "first review" since they already bought before; emphasize loyalty rewards).

**WooCommerce Memberships hooks?** `wc_memberships_user_membership_status_changed` — branch on `$new_status` to subscribe to relevant lists ("new member" vs "expired" vs "renewed").

**Can I unsubscribe customers from the post-purchase list after Day 14?** Add a final "Remove from list" step at the end of the automation. Or use a "Move to" step to add them to your general newsletter (they're now a paying customer; appropriate to nurture them long-term).

**What about transactional emails (order confirmation, shipping notification)?** Keep those in WooCommerce — that's its job, and they need to be 100% reliable per-order. The AcelleMail flow is for *marketing* follow-up.

**Can I include the order line items in the welcome email?** Yes — fetch `$order->get_items()` and pass them as a JSON-encoded `LINE_ITEMS` custom field, then parse in the AcelleMail template via merge tags + a small JS rendering. For most stores, just naming the `HEADLINE_PRODUCT` (the highest-value line) is enough.

**How do I A/B test the post-purchase sequence?** AcelleMail supports automation-level A/B (split subscribers 50/50, send different sequences). See [A/B Testing Email Subject Lines](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates) for the principles.

**Multilingual store?** Pass the order's locale (`$order->get_meta('_locale')` if set, else `get_locale()`) as a custom field. Branch the automation on it to send the right-language sequence.

## Related articles

- [Integrating AcelleMail with WordPress and WooCommerce](https://acellesend.com/kb/articles/integrating-acellemail-with-wordpress-and-woocommerce) — the master overview
- [WordPress Subscriber Sync](https://acellesend.com/kb/articles/wordpress-subscriber-sync) — sibling pattern for general user registration
- [Post-Purchase Follow-Up Automation](https://acellesend.com/kb/articles/post-purchase-follow-up-automation) — the AcelleMail automation template that consumes these events
- [Creating an Abandoned Cart Email Sequence](https://acellesend.com/kb/articles/creating-an-abandoned-cart-email-sequence) — for pre-purchase WooCommerce events
- [Advanced Automation Triggers and Conditions](https://acellesend.com/kb/articles/advanced-automation-triggers-and-conditions) — for the conditional branching used in Step 3
- [Getting Started with the AcelleMail REST API](https://acellesend.com/kb/articles/getting-started-with-the-acellemail-rest-api) — API fundamentals
- [REST API Authentication and Endpoints](https://acellesend.com/kb/articles/rest-api-authentication-and-endpoints) — subscriber + tag endpoints used in Step 4
- [A/B Testing Email Subject Lines](https://acellesend.com/kb/articles/ab-testing-email-subject-lines-for-better-open-rates) — optimise sequence performance
- [Setting Up Queue Workers and Cron Jobs](https://acellesend.com/kb/articles/setting-up-queue-workers-and-cron-jobs) — automation depends on the queue

---

## Related articles

- [Connecting Stripe for Subscription Billing](https://acellesend.com/kb/articles/connecting-stripe-for-subscription-billing): AcelleMail's Cashier ships Stripe as the first-class subscription gateway. This guide walks the connection end-to-end: API key configuration, the four webhook events that must be subscribed, the webhook URL location, and test-vs-live mode discipline. Covers both subscription billing (for SaaS operators) and one-off payment use cases.
- [Integrating AcelleMail with WordPress and WooCommerce](https://acellesend.com/kb/articles/integrating-acellemail-with-wordpress-and-woocommerce): WordPress + WooCommerce is the most common stack people connect to AcelleMail. There's no first-class plugin — instead, you wire up the REST API from a few WordPress hooks. This guide walks the three integration patterns (user registration, WooCommerce purchase, optional unsubscribe sync) end-to-end, plus the choice of "API from theme/plugin" vs "embedded signup form" vs "third-party Zapier".
- [WordPress Subscriber Sync](https://acellesend.com/kb/articles/wordpress-subscriber-sync): Wire up the WordPress user_register hook to push every new account into an AcelleMail list. Twenty lines of PHP, no plugin dependency, real-time. This guide covers the production-ready snippet (with timeout / async / error handling) and the four common gotchas — field-name case, secrets in version control, slow page loads, duplicate subscribers.
- [Zapier Integration Guide for AcelleMail](https://acellesend.com/kb/articles/zapier-integration-guide): AcelleMail doesn't ship a native Zapier app, but the REST API + webhook system gives you everything you need to wire AcelleMail to the 7,000+ apps on Zapier. This guide walks both directions — AcelleMail as a Zapier action (POST to API), and AcelleMail as a Zapier trigger (webhook → Zap). Plus 6 production-tested Zap recipes.
- [BIMI with VMC — Cost, Process, and Whether It's Worth It](https://acellesend.com/kb/articles/bimi-with-vmc-cost-walkthrough): VMC (Verified Mark Certificate) costs $1,500/yr per domain and takes 4-8 weeks to obtain. This guide walks the cost math, the trademark requirements, the alternative paths, and a decision framework for whether VMC is right for your sending program.
