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

# Re-Engagement Campaign Builder

> Identify dormant Mailchimp subscribers by signup source and interest tags, then generate personalized multi-step re-engagement email sequences using Mavera Agent and Generate

### Scenario

Subscribers who haven't opened an email in 90+ days are effectively lost — unless you can win them back. You pull inactive subscribers from Mailchimp, analyze their original signup source and interest tags, then use Mavera to generate personalized re-engagement email sequences per segment. The result is a multi-step re-engagement campaign that addresses why each subscriber went dormant.

### Architecture

```mermaid theme={"dark"}
flowchart LR
A["GET /lists/{id}/members inactive 90d+"] --> B["Group by signup source + interests"] --> C["POST /api/v1/mave/chat"] --> D["POST /api/v1/generations"] --> E["Re-engagement sequences"]
```

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests, time
  from datetime import datetime, timedelta
  from collections import defaultdict

  MC_KEY = os.environ["MAILCHIMP_API_KEY"]
  MC_DC = os.environ["MAILCHIMP_DC"]
  MV = os.environ["MAVERA_API_KEY"]
  MB = "https://app.mavera.io/api/v1"
  MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
  MC_BASE = f"https://{MC_DC}.api.mailchimp.com/3.0"
  mc_auth = ("anystring", MC_KEY)

  lists_r = requests.get(f"{MC_BASE}/lists", auth=mc_auth, params={"count": 1})
  LIST_ID = lists_r.json()["lists"][0]["id"]

  cutoff = (datetime.now() - timedelta(days=90)).strftime("%Y-%m-%dT%H:%M:%S+00:00")

  inactive = []
  offset = 0
  while len(inactive) < 500:
      r = requests.get(
          f"{MC_BASE}/lists/{LIST_ID}/members",
          auth=mc_auth,
          params={
              "count": 100,
              "offset": offset,
              "status": "subscribed",
              "since_last_changed": "2025-01-01T00:00:00+00:00",
              "fields": "members.email_address,members.stats,members.source,members.interests,members.merge_fields,members.timestamp_signup",
          },
      )
      r.raise_for_status()
      members = r.json().get("members", [])
      if not members:
          break

      for m in members:
          avg_open = m.get("stats", {}).get("avg_open_rate", 0)
          if avg_open < 0.02:
              inactive.append(m)

      offset += 100
      time.sleep(0.5)

  print(f"Found {len(inactive)} inactive subscribers (90d+ no opens)\n")

  source_groups = defaultdict(list)
  for m in inactive:
      source = m.get("source", "unknown") or "unknown"
      source_groups[source].append(m)

  source_summary = "\n".join(
      f"- {source}: {len(members)} subscribers"
      for source, members in sorted(source_groups.items(), key=lambda x: -len(x[1]))[:8]
  )

  mave = requests.post(f"{MB}/mave/chat", headers=MH, json={
      "message": f"""Analyze why email subscribers go dormant and recommend re-engagement strategies per signup source.

  INACTIVE SUBSCRIBERS (no opens in 90+ days): {len(inactive)} total

  BY SIGNUP SOURCE:
  {source_summary}

  For each source group:
  1. Why do subscribers from this source tend to go dormant?
  2. What originally attracted them?
  3. Best re-engagement angle (nostalgia, new value, curiosity, FOMO, direct ask)
  4. Should we try to re-engage or clean the list?"""
  }).json()

  print("--- Dormancy Analysis ---")
  print(mave.get("content", "")[:1500])
  print()

  for source, members in sorted(source_groups.items(), key=lambda x: -len(x[1]))[:3]:
      gen = requests.post(f"{MB}/generations", headers=MH, json={
          "prompt": (
              f"Create a 3-email re-engagement sequence for {len(members)} dormant subscribers "
              f"who originally signed up via '{source}'. They haven't opened an email in 90+ days.\n\n"
              f"For each email in the sequence:\n"
              f"1. Subject line\n"
              f"2. Preview text\n"
              f"3. Body (100-150 words)\n"
              f"4. CTA\n"
              f"5. Send timing (days after previous)\n\n"
              f"Sequence strategy:\n"
              f"  Email 1: Soft re-introduction (don't be desperate)\n"
              f"  Email 2: Value demonstration (show what they're missing)\n"
              f"  Email 3: Last chance (unsubscribe if no response)\n\n"
              f"Tone: Warm but not pushy. Acknowledge the gap."
          ),
      }).json()

      print(f"{'='*50}")
      print(f"Re-engagement: '{source}' subscribers ({len(members)})")
      print(f"{'='*50}")
      print(gen.get("output", gen.get("content", ""))[:800])
      print()
      time.sleep(0.5)
  ```

  ```javascript JavaScript theme={"dark"}
  const MC_KEY = process.env.MAILCHIMP_API_KEY;
  const MC_DC = process.env.MAILCHIMP_DC;
  const MV = process.env.MAVERA_API_KEY;
  const MB = "https://app.mavera.io/api/v1";
  const MH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
  const MC_BASE = `https://${MC_DC}.api.mailchimp.com/3.0`;
  const mcAuth = "Basic " + Buffer.from(`anystring:${MC_KEY}`).toString("base64");

  const listsRes = await fetch(`${MC_BASE}/lists?count=1`, { headers: { Authorization: mcAuth } }).then((r) => r.json());
  const LIST_ID = listsRes.lists[0].id;

  const inactive = [];
  let offset = 0;
  while (inactive.length < 500) {
    const res = await fetch(
      `${MC_BASE}/lists/${LIST_ID}/members?count=100&offset=${offset}&status=subscribed`,
      { headers: { Authorization: mcAuth } }
    ).then((r) => r.json());
    const members = res.members || [];
    if (!members.length) break;
    for (const m of members) {
      if ((m.stats?.avg_open_rate || 0) < 0.02) inactive.push(m);
    }
    offset += 100;
    await new Promise((r) => setTimeout(r, 500));
  }

  console.log(`Found ${inactive.length} inactive subscribers\n`);

  const sourceGroups = {};
  for (const m of inactive) {
    const source = m.source || "unknown";
    sourceGroups[source] ??= [];
    sourceGroups[source].push(m);
  }

  const sorted = Object.entries(sourceGroups).sort(([, a], [, b]) => b.length - a.length);
  const summary = sorted.slice(0, 8).map(([s, m]) => `- ${s}: ${m.length} subscribers`).join("\n");

  const mave = await fetch(`${MB}/mave/chat`, { method: "POST", headers: MH,
    body: JSON.stringify({
      message: `Analyze dormant subscriber patterns.\n\nInactive (90d+): ${inactive.length}\n\nBy source:\n${summary}\n\nFor each source: 1) Why dormant? 2) Original attraction 3) Re-engagement angle 4) Re-engage or clean?`,
    }),
  }).then((r) => r.json());

  console.log("--- Dormancy Analysis ---");
  console.log((mave.content || "").slice(0, 1500));

  for (const [source, members] of sorted.slice(0, 3)) {
    const gen = await fetch(`${MB}/generations`, { method: "POST", headers: MH,
      body: JSON.stringify({
        prompt: `3-email re-engagement for ${members.length} dormant subs from '${source}' (90d+ no opens). Each email: subject, preview, body (100-150 words), CTA, timing. Strategy: (1) soft re-intro (2) value demo (3) last chance.`,
      }),
    }).then((r) => r.json());

    console.log(`\n${"=".repeat(50)}`);
    console.log(`Re-engagement: '${source}' (${members.length})`);
    console.log((gen.output || gen.content || "").slice(0, 800));
    await new Promise((r) => setTimeout(r, 500));
  }
  ```
</CodeGroup>

### Example Output

```text theme={"dark"}
Found 1,847 inactive subscribers (90d+ no opens)

--- Dormancy Analysis ---
## Source: "Import" (620 subscribers)
These were likely bulk-imported from another tool or event list. They
never had an organic relationship with your emails — re-engagement
rate will be low (~3-5%). Strategy: one "do you still want to hear
from us?" email, then clean the list.

## Source: "Signup Form" (480 subscribers)
Organic signups who went dormant. They once cared enough to fill out
a form. Re-engagement angle: remind them what they signed up for.
Show what's changed since they left.

==================================================
Re-engagement: 'Signup Form' subscribers (480)
==================================================
**Email 1: Soft Re-introduction (Day 0)**
Subject: We noticed you've been quiet
Preview: No guilt trip — just checking in.

Hi [NAME],

It's been a while since you opened one of our emails. No judgment
— inboxes are overwhelming.

Here's what happened while you were away: we shipped [Feature],
redesigned [Page], and published [Popular Post Title].

If any of that sounds interesting, just click below. If not, no
hard feelings.

→ [Show Me What I Missed]

**Email 2: Value Demo (Day 4)**
Subject: This saved our users 6 hours last week
Preview: The feature you might not know about.

**Email 3: Last Chance (Day 10)**
Subject: Should we stop emailing you?
Preview: Click to stay — or we'll unsubscribe you automatically.
```

### Error Handling

<AccordionGroup>
  <Accordion title="Filtering inactive subscribers">Mailchimp doesn't have a direct 'last open date' filter in the Members API. The code uses `avg_open_rate < 0.02` as a proxy for dormancy. For more precise filtering, use Mailchimp's built-in segment builder with 'Did not open' conditions, then query that segment.</Accordion>
  <Accordion title="Pagination with offset">The Members API uses `offset` pagination (not cursor-based). For lists with 100k+ members, this can be slow. Consider using the batch endpoint or exporting via the Export API for large lists.</Accordion>
  <Accordion title="Re-engagement compliance">Always include an unsubscribe link in re-engagement emails. The "last chance" email should honor the implicit opt-out — if they don't engage, move them to a cleaned or archived state.</Accordion>
</AccordionGroup>

***

## What's Next

<CardGroup cols={2}>
  <Card title="Mailchimp Integration" icon="envelope" href="/integrations/mailchimp">
    Back to Mailchimp integration overview
  </Card>

  <Card title="Interest Content Generation" icon="tags" href="/integrations/mailchimp/interest-content-generation">
    Generate content by audience interest
  </Card>

  <Card title="Open Rate Prediction" icon="chart-line-up" href="/integrations/mailchimp/open-rate-prediction">
    Test subject lines before sending
  </Card>

  <Card title="Mave Agent" icon="brain" href="/api-reference/mave">
    Full reference for POST /api/v1/mave/chat
  </Card>
</CardGroup>
