Skip to main content

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.

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

Code

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)

Example Output

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

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.
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.
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.

What’s Next

Mailchimp Integration

Back to Mailchimp integration overview

Interest Content Generation

Generate content by audience interest

Open Rate Prediction

Test subject lines before sending

Mave Agent

Full reference for POST /api/v1/mave/chat