Skip to content

anianroid/deadletter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

deadletter

Suppress bounced leads and rotate contacts across cold email campaigns.

Named after the CS concept of dead-letter queues -- messages that can't be delivered get routed to a separate queue instead of being retried forever.

The problem

Your cold email tool has a 1,000 contact cap. You have 5,000 leads.

You run a campaign, 50 emails bounce, but those bounced addresses stay in the system. Next campaign, you re-target "non-openers" -- but non-openers includes the bounced emails (they also have email_open_count = 0). Your bounce rate spikes, sender reputation drops, deliverability tanks.

Meanwhile, completed campaigns still occupy contact slots. You can't add new leads without manually removing old ones.

What deadletter does

  1. Central lead vault -- one Postgres database with every contact across all campaigns
  2. Suppression list -- bounced, DND, unsubscribed, and spam complaints tracked across campaigns
  3. Auto-detection -- syncs campaign data and automatically flags bounced emails
  4. Suppression guard -- every lead upload is filtered through the vault before reaching Instantly
  5. Contact rotation -- backup completed campaigns to the vault, free up slots, re-enroll clean leads

Quick start

# Clone and setup
git clone https://github.com/anianroid/deadletter.git
cd deadletter
bash scripts/setup.sh

# Check what's configured
.venv/bin/python scripts/deadletter.py setup

# Edit .env with your credentials
# DATABASE_URL = any Postgres (Neon free tier works: https://neon.tech)
# INSTANTLY_API_KEY = from Instantly settings > integrations

# Initialize database + import
.venv/bin/python scripts/deadletter.py init
.venv/bin/python scripts/deadletter.py import --instantly -v

# Check stats
.venv/bin/python scripts/deadletter.py stats

Install

Claude Code

git clone https://github.com/anianroid/deadletter.git
cd deadletter
bash scripts/setup.sh

Then tell Claude: "Set up deadletter for me." It will walk you through connecting your database and Instantly account.

Claude Cowork

  1. Open your Cowork server
  2. Run this in a channel:
Clone https://github.com/anianroid/deadletter.git and set it up for me.

Claude will clone the repo, run the setup script, and guide you through adding your Neon database URL and Instantly API key. No terminal knowledge needed.

Adding the skill to an existing project

If you already have a project and just want the deadletter skill:

# From your project root
git clone https://github.com/anianroid/deadletter.git /tmp/deadletter
cp -r /tmp/deadletter/skills/deadletter .claude/skills/deadletter
cp /tmp/deadletter/scripts/deadletter.py scripts/deadletter.py
cp /tmp/deadletter/scripts/requirements.txt scripts/deadletter-requirements.txt
pip install -r scripts/deadletter-requirements.txt

Then add to your project's CLAUDE.md:

## Lead Suppression
See `.claude/skills/deadletter/SKILL.md` for lead vault and suppression guard commands.

Using with Claude Code / Cowork

Once installed, just talk to Claude in plain language:

  • "I have a list of leads, make sure none of them are bounced"
  • "Add these leads to my Skills Launch campaign"
  • "I'm hitting the contact limit, can you free up space?"
  • "Is john@example.com safe to email?"
  • "Show me my campaigns"
  • "Set up deadletter for me"

Claude will translate these into the right commands and explain results without showing UUIDs or raw terminal output.

Commands

Command Description
setup Check what's configured and what's missing
campaigns List all campaigns by name
init Create database tables
import --instantly Import leads from all Instantly campaigns
import --csv leads.csv Import leads from CSV
sync Pull latest campaign data from Instantly
suppress Auto-detect bounced leads, add to suppression list
suppress --add email --reason bounced Manually suppress an email
check email@example.com Check if an email is suppressed
guard --csv leads.csv --name "My Campaign" Upload leads with automatic suppression filtering
rotate --dry-run Preview which campaigns can free up slots
rotate --confirm Backup completed campaigns, free Instantly slots
enroll --name "My Campaign" --limit 50 Pull clean leads from vault into a campaign
export --csv leads.csv --clean Export non-suppressed leads to CSV
stats Show vault summary

All commands that accept --campaign-id also accept --name for campaign name matching (partial match, case-insensitive).

Typical workflow

Adding leads safely (the primary workflow)

# Upload leads with automatic suppression check
.venv/bin/python scripts/deadletter.py guard --csv leads.csv --name "My Campaign" --dry-run

# If the dry run looks good, upload for real
.venv/bin/python scripts/deadletter.py guard --csv leads.csv --name "My Campaign"

The guard command checks every lead against the suppression list and campaign history before uploading. Bounced, DND, unsubscribed, and duplicate leads are automatically blocked.

Before a new campaign

# Make sure vault is current
.venv/bin/python scripts/deadletter.py sync -v
.venv/bin/python scripts/deadletter.py suppress -v

# Enroll clean leads (skips suppressed + already-in-campaign)
.venv/bin/python scripts/deadletter.py enroll --name "My Campaign" --limit 100

When you hit the contact cap

# Preview what would be freed
.venv/bin/python scripts/deadletter.py rotate --dry-run -v

# Backup to vault + remove from Instantly
.venv/bin/python scripts/deadletter.py rotate --confirm -v

After a campaign runs

# Sync latest data (opens, replies, bounces)
.venv/bin/python scripts/deadletter.py sync --name "My Campaign" -v

# Catch new bounces
.venv/bin/python scripts/deadletter.py suppress -v

Database schema

Four tables in any Postgres database:

  • leads -- single source of truth for every contact (dedup on email)
  • suppression_list -- emails that must never be contacted again, with reason
  • campaign_history -- per-lead-per-campaign record (status, opens, replies)
  • campaign_metadata -- campaign-level info (name, lead count, sync status)

See skills/deadletter/references/SCHEMA.md for full column details.

Requirements

  • Python 3.8+
  • Any Postgres database (Neon free tier works)
  • Instantly API key

Provider support

Currently supports Instantly. The architecture is provider-agnostic -- contributions for Smartlead, Lemlist, and other platforms are welcome.

License

Apache 2.0

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages