> ## 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: Re-engagement Campaign for Inactive Users

> Win back customers who haven't engaged in 30, 60, or 90 days

Users drift. This recipe catches them at 30, 60, and 90 days of inactivity with progressively more engaged messaging — one last chance to remind them why they signed up before you quietly remove them from your active list.

<Info>
  Before you start: your `users` table needs a `last_login_at` (or similar activity timestamp), and you need a [configured email service](/how-tos/send-emails).
</Info>

## The Flow at a Glance

<CardGroup cols={3}>
  <Card title="Day 30" icon="lightbulb">
    "What's new since you left"
  </Card>

  <Card title="Day 60" icon="hand">
    "Do you need help?"
  </Card>

  <Card title="Day 90" icon="door-open">
    Final offer or clean goodbye
  </Card>
</CardGroup>

## Step 1: Create the Workflow

<Steps>
  <Step title="Open Workflows">
    Go to **Workflows > New Workflow**. Name it `Re-engagement Campaign`.
  </Step>

  <Step title="Add a Schedule trigger">
    Set to run **daily at 10 AM UTC** (or whatever time zone makes sense for your audience).

    <Tip>
      Daily is enough for re-engagement — hourly would be overkill. Pick a time when your audience typically opens email, not 3 AM.
    </Tip>
  </Step>
</Steps>

## Step 2: The 30-Day Cohort

<Steps>
  <Step title="Add a Database query action">
    Find users whose last activity was exactly 30 days ago (within a 1-hour window to match the schedule):

    ```sql theme={null}
    SELECT id, email, name, last_login_at
    FROM users
    WHERE last_login_at BETWEEN now() - interval '30 days 1 hour'
                            AND now() - interval '30 days'
      AND reengagement_30d_sent_at IS NULL
      AND marketing_opt_in = true
    ```
  </Step>

  <Step title="Loop and send">
    Add a **For Each** step over the results. Inside, add a **Send Email** action:

    * **Subject:** `{{item.name}}, here's what you missed`
    * **Body:** Focus on what's new since they were last active. New features, popular content, customer wins. Make it about value, not guilt.
  </Step>

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

    ```sql theme={null}
    UPDATE users SET reengagement_30d_sent_at = now() WHERE id = '{{item.id}}'
    ```
  </Step>
</Steps>

## Step 3: The 60-Day Cohort

Clone the 30-day block. Change the query to target 60 days:

```sql theme={null}
SELECT id, email, name, last_login_at
FROM users
WHERE last_login_at BETWEEN now() - interval '60 days 1 hour'
                        AND now() - interval '60 days'
  AND reengagement_60d_sent_at IS NULL
  AND marketing_opt_in = true
```

Shift the messaging to "do you need help?" — offer an onboarding call, a support chat link, or a link to the docs. The assumption is they hit a wall, not that they didn't see the value.

## Step 4: The 90-Day Cohort

At 90 days, it's either a clean goodbye or a final offer. Pick one:

* **Final offer:** discount, extended free trial, white-glove onboarding
* **Clean goodbye:** "We'd love to have you back anytime — here's a link to log in"

Either way, after 90 days with no response, flip `marketing_opt_in = false` to stop active marketing. Keep the record in your CRM for future relationships.

## Messaging Tone by Cohort

| Cohort  | Tone                     | Primary CTA                     |
| ------- | ------------------------ | ------------------------------- |
| 30 days | Curious, informative     | Log back in, see what's new     |
| 60 days | Helpful, supportive      | Book a call, reply for help     |
| 90 days | Respectful, low-pressure | One last offer or quiet goodbye |

<Tip>
  Track email opens and clicks to separate "alive but quiet" from "truly gone." If someone opens the 30-day email but doesn't log in, they're paying attention — nurture them longer. If they never open any of the three, they're done.
</Tip>

## Respecting Unsubscribes

Always include an unsubscribe link and honor your suppression list. Re-engagement campaigns hit users who are already half-checked-out; one wrong email and they unsubscribe or mark as spam.

Add a **Check Suppressions** step before each Send Email, or use your provider's built-in suppression handling. See [Email Suppressions](/how-tos/email-suppressions).

<Warning>
  Sending re-engagement emails to users who already unsubscribed is the fastest way to blow up your sender reputation. Always check suppressions first.
</Warning>

## A/B Testing Subject Lines

Low-engagement cohorts are a great place to test subject lines — you're already reaching users unlikely to convert, so nothing to lose.

Try:

* Curiosity-based: "A question about your account"
* Direct: "We miss you, `{{item.name}}`"
* Benefit-led: "Three new features since you've been gone"
* Human: "From Abe at Acme — hope you're well"

Look at open rates after a week and keep the winner.

## Verify It Worked

1. Pick a test user
2. Manually set `last_login_at` to 30 days ago: `UPDATE users SET last_login_at = now() - interval '30 days' WHERE email = 'test@example.com'`
3. Wait for the next scheduled run (or trigger manually from **Workflows > Runs**)
4. Confirm the 30-day email arrives
5. Confirm `reengagement_30d_sent_at` is now populated
6. Re-run the workflow — the test user should not receive a duplicate

## Troubleshooting

<AccordionGroup>
  <Accordion title="Users getting multiple emails in the same cohort">
    The `reengagement_Xd_sent_at` check isn't in the query, or the UPDATE isn't running. Look at **Workflows > Runs** and inspect the query output — if the same user appears in the results across two consecutive runs, the UPDATE silently failed. Check permissions on the users table.
  </Accordion>

  <Accordion title="Users appearing in the wrong cohort">
    Interval math is easy to get wrong. The `BETWEEN now() - interval '30 days 1 hour' AND now() - interval '30 days'` pattern catches a 1-hour window ending 30 days ago. If your schedule runs hourly, widen to `'30 days 1 hour'`; if daily, widen to `'31 days' AND '30 days'`. Always match the window to the schedule cadence.
  </Accordion>

  <Accordion title="No one is engaging with the emails">
    Re-engagement is hard — a 5-10% open rate is typical, 1-2% click-through is normal. If you're at 0%, the issue is subject lines (too generic), sender reputation (check deliverability), or the offer (too weak). A/B test subject lines first.
  </Accordion>

  <Accordion title="Workflow is timing out with large user bases">
    If you have 100k+ inactive users, looping inline will timeout. Batch it: send to 500 users per run, schedule more frequently, or push the batch to a queue with worker processing. See [Workflows](/how-tos/workflows) for long-running job patterns.
  </Accordion>

  <Accordion title="Re-engagement emails going to spam">
    Low-engagement sends hurt sender reputation. Warm up carefully: start with the most-engaged dormant users (those who opened recently), skip anyone with prior bounces, and cap daily volume. Consider a [dedicated IP](/how-tos/dedicated-email-ip) if re-engagement is a large portion of your volume.
  </Accordion>
</AccordionGroup>

## What's Next?

<CardGroup cols={2}>
  <Card title="Email Suppressions" icon="ban" href="/how-tos/email-suppressions">
    Handle unsubscribes and bounces properly
  </Card>

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