> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hiveku.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflow Recipe: Abandoned Cart Recovery

> Automatically email customers who leave items in their shopping cart

E-commerce sites typically recover 10-30% of abandoned carts with a well-timed reminder. This recipe sends a gentle nudge an hour after someone walks away — and optionally a discount follow-up the next day.

<Info>
  Before you start: your site needs a `carts` (or `orders`) table with a status column and a user email, plus a [configured email service](/how-tos/send-emails).
</Info>

## The Flow at a Glance

<CardGroup cols={3}>
  <Card title="1 hour" icon="envelope">
    Gentle reminder
  </Card>

  <Card title="24 hours" icon="tag">
    Add a small discount
  </Card>

  <Card title="3 days" icon="hand">
    Final last-chance
  </Card>
</CardGroup>

## Step 1: Create the Workflow

<Steps>
  <Step title="Open Workflows">
    Go to **Workflows > New Workflow**. Name it `Abandoned Cart Recovery`.
  </Step>

  <Step title="Add a Schedule trigger">
    Click **Add Trigger > Schedule**. Set it to run **every hour** (`rate(1 hour)`).

    <Tip>
      Hourly works well for most stores. Busy stores with thousands of carts per day can drop to every 30 minutes; quieter stores can schedule less often.
    </Tip>
  </Step>
</Steps>

## Step 2: Find Abandoned Carts

<Steps>
  <Step title="Add a Database query action">
    Click **+ Add Action > Database Query**. Paste this SQL:

    ```sql theme={null}
    SELECT c.id, c.user_email, c.items, c.total, c.updated_at
    FROM carts c
    WHERE c.status = 'abandoned'
      AND c.updated_at < now() - interval '1 hour'
      AND c.updated_at > now() - interval '24 hours'
      AND c.reminder_sent_at IS NULL
    ```

    This returns carts that were abandoned between 1 and 24 hours ago and haven't received an email yet.
  </Step>

  <Step title="Loop over results">
    Add a **For Each** step over the query result. Each iteration represents one cart to recover.
  </Step>
</Steps>

## Step 3: Send the First Email

<Steps>
  <Step title="Add a Send Email action inside the loop">
    * **To:** `{{item.user_email}}`
    * **Subject:** `You left something behind!`
    * **Body:** Show the cart contents, total, and a big button back to checkout. Keep it short and warm.
  </Step>

  <Step title="Personalize the body">
    Pull fields from the cart row:

    ```
    Hey {{item.customer_name || 'there'}},

    Your cart is still waiting — {{item.items[0].name}} and more. 
    Total: ${{item.total}}.

    Pick up where you left off: https://yoursite.com/checkout?cart={{item.id}}
    ```
  </Step>

  <Step title="Mark the reminder as sent">
    Add a **Database Update** action:

    ```sql theme={null}
    UPDATE carts SET reminder_sent_at = now() WHERE id = '{{item.id}}'
    ```

    This prevents the same cart from receiving the email every hour.
  </Step>
</Steps>

## Step 4: Day 2 Follow-up with Discount

Clone the first action set, but change the query to target carts that were reminded 24 hours ago and still haven't converted:

```sql theme={null}
SELECT c.id, c.user_email, c.items, c.total
FROM carts c
WHERE c.status = 'abandoned'
  AND c.reminder_sent_at < now() - interval '24 hours'
  AND c.discount_sent_at IS NULL
```

Send a follow-up with a small incentive (e.g., 10% off code `COMEBACK10`) and update `discount_sent_at`.

## Step 5: Day 3 Last-Chance

A final message 3 days after abandon, with last-chance language. Skip anyone who already converted by filtering `status != 'completed'`.

## Stop Sending When They Purchase

Every email action should check the current cart status first. Add a **Condition** step before each Send Email:

```
{{item.status}} == 'abandoned'
```

If the customer completed checkout between the schedule runs, the condition fails and the email is skipped.

<Warning>
  Don't be pushy. Three emails is the cap. More than that hurts your sender reputation, drives unsubscribes, and flags you as spam — which lowers deliverability for every other email you send.
</Warning>

## Personalization Ideas

* Include product images pulled from `{{item.items[0].image_url}}`
* Reference the customer's location or last purchase if you have the data
* Tailor subject line to cart value — "Your \$200 cart is waiting" converts better than generic copy
* Use the customer's first name, not "Dear Customer"

## Tracking Effectiveness

Email opens and clicks tell you what's working.

* **Open rate on Email 1** — subject line quality
* **Click-through on Email 2** — did the discount move them?
* **Conversion rate from email → checkout** — the only metric that really matters

Wire up [Email Webhooks](/how-tos/email-webhooks) to log open/click events per cart so you can A/B test subject lines and offers.

## Verify It Worked

1. Add a test item to a cart while signed in as a test user
2. Leave the session idle for just over an hour
3. Wait for the next workflow run (or trigger manually from the Workflows > Runs tab)
4. Confirm the email arrives at the test address
5. Click the checkout link — it should return you to the exact cart

## Troubleshooting

<AccordionGroup>
  <Accordion title="No emails are sending">
    Check your cart status filter. Many sites store abandoned carts as `active` or `pending` rather than `abandoned` — run the SELECT manually in the database explorer and confirm it returns rows. Also check the time range: if nothing is between 1 and 24 hours old, the query is correct but there's nothing to send.
  </Accordion>

  <Accordion title="Customers are getting the same email multiple times">
    The `reminder_sent_at` update isn't being written. Open **Workflows > Runs**, click a recent run, and check the UPDATE action output. Common cause: a syntax error in the UPDATE that silently fails. Wrap it in a try/catch step and log errors.
  </Accordion>

  <Accordion title="Customers reporting emails as spam">
    Your messaging is too pushy, or frequency is too high. Reduce to two emails max, soften the language ("Your cart is still here" instead of "Don't miss out!"), and always include an unsubscribe link. Check your sender reputation in your ESP dashboard.
  </Accordion>

  <Accordion title="Emails arriving for completed carts">
    Add a final status check right before the Send Email action. The schedule fires every hour — between the query and the send, a customer might have completed checkout. The Condition step on `status == 'abandoned'` guards against this.
  </Accordion>

  <Accordion title="Personalization tokens showing as literal text">
    The query didn't return the field, or the loop item path is wrong. In **Runs**, inspect the query output — if `customer_name` is null, add a fallback like `{{item.customer_name || 'there'}}`. Confirm you're referencing `{{item.x}}` inside the loop, not `{{trigger.x}}`.
  </Accordion>
</AccordionGroup>

## What's Next?

<CardGroup cols={2}>
  <Card title="Workflows Basics" icon="diagram-project" href="/how-tos/workflows">
    How triggers, actions, and runs fit together
  </Card>

  <Card title="Email Webhooks" icon="webhook" href="/how-tos/email-webhooks">
    Track opens, clicks, and unsubscribes
  </Card>
</CardGroup>
