Cleanbox
Features Helpdesk Blog Pricing Contact
Sign in Start free trial
api developer automation

Auto-Block Repeat Offenders: Building a Contact Management Bot

Your contact list grows with every email. Most contacts are fine — legitimate senders, services you signed up for, colleagues. But some are spam sources that keep sending even after their emails are rejected by your spam filter. They accumulate "unset" contacts with high email counts and zero value.

In this guide, we build a Python bot that reviews your contacts, blocks repeat spam offenders, and optionally whitelists trusted senders — all through the Cleanbox API.

The strategy

Contact profileActionLogic
Unset, 50+ emails, mostly spamBlockHigh volume + never interacted = spam
Unset, from known junk TLDsBlock.xyz, .top, .click, .buzz domains
Unset, Finance or Business category, 10+ emailsWhitelistImportant category + consistent sending = trusted
Unset, Shopping category, has subscriptionsKeep unsetMay want to unsubscribe, not block

Step 1: Fetch all unset contacts

import requests
import re

API_BASE = "https://api.cleanbox.app/v1"
API_KEY  = "your_api_key_here"
HEADERS  = {"Authorization": f"Bearer {API_KEY}"}

def get(path, params=None):
    r = requests.get(f"{API_BASE}{path}", headers=HEADERS, params=params)
    r.raise_for_status()
    return r.json()

def put(path, data):
    r = requests.put(f"{API_BASE}{path}", headers=HEADERS, json=data)
    r.raise_for_status()
    return r.json()

def get_all_unset_contacts():
    """Fetch all contacts with state 'unset', paginated."""
    contacts = []
    page = 1

    while True:
        data = get("/contacts", {
            "state": "unset",
            "sort": "emailCount",
            "dir": "desc",
            "page": page,
            "per_page": 100
        })

        contacts.extend(data["data"])

        if page >= data["total_pages"]:
            break
        page += 1

    return contacts

We use GET /v1/contacts?state=unset&sort=emailCount&dir=desc to get all unset contacts sorted by email volume. The highest-volume contacts are reviewed first.

Step 2: Define the rules

JUNK_TLDS = {".xyz", ".top", ".click", ".buzz", ".icu", ".club", ".loan", ".work"}
TRUSTED_CATEGORIES = {"Finance", "Business Services", "Productivity & Tools"}
BLOCK_THRESHOLD = 50  # emails from unset contact
WHITELIST_THRESHOLD = 10  # emails from trusted category

def extract_tld(email):
    """Extract TLD from email address."""
    domain = email.split("@")[1] if "@" in email else ""
    parts = domain.rsplit(".", 1)
    return f".{parts[-1]}" if len(parts) > 1 else ""

def classify_contact(contact):
    """Determine what action to take for a contact."""
    email = contact["email"]
    count = contact["email_count"]
    category = contact.get("category", {})
    category_name = category.get("name", "") if category else ""
    tld = extract_tld(email)

    # Rule 1: Block junk TLDs
    if tld in JUNK_TLDS:
        return "block", f"Junk TLD: {tld}"

    # Rule 2: Block high-volume unset contacts
    if count >= BLOCK_THRESHOLD:
        return "block", f"High volume: {count} emails, never interacted"

    # Rule 3: Whitelist trusted categories with consistent sending
    if category_name in TRUSTED_CATEGORIES and count >= WHITELIST_THRESHOLD:
        return "whitelist", f"Trusted category: {category_name}, {count} emails"

    # Default: no action
    return None, None

Step 3: Apply actions

def apply_actions(contacts, dry_run=True):
    """Review contacts and apply block/whitelist actions."""
    stats = {"blocked": 0, "whitelisted": 0, "skipped": 0}

    for contact in contacts:
        action, reason = classify_contact(contact)

        if action == "block":
            print(f"  BLOCK  {contact['email']} - {reason}")
            if not dry_run:
                put(f"/contacts/{contact['uuid']}", {"state": "blocked"})
            stats["blocked"] += 1

        elif action == "whitelist":
            print(f"  TRUST  {contact['email']} - {reason}")
            if not dry_run:
                put(f"/contacts/{contact['uuid']}", {"state": "whitelisted"})
            stats["whitelisted"] += 1

        else:
            stats["skipped"] += 1

    return stats

The PUT /v1/contacts/{uuid} endpoint updates the contact state. Setting it to "blocked" means all future email from this sender is rejected. Setting it to "whitelisted" means their email bypasses spam checks and Shield rules.

Step 4: Run with dry-run safety

import sys

def main():
    dry_run = "--apply" not in sys.argv

    if dry_run:
        print("DRY RUN - no changes will be made (use --apply to execute)")
    else:
        print("LIVE RUN - changes will be applied")

    print("
Fetching unset contacts...")
    contacts = get_all_unset_contacts()
    print(f"Found {len(contacts)} unset contacts
")

    stats = apply_actions(contacts, dry_run=dry_run)

    print(f"
Summary:")
    print(f"  Blocked:     {stats['blocked']}")
    print(f"  Whitelisted: {stats['whitelisted']}")
    print(f"  Skipped:     {stats['skipped']}")

    if dry_run:
        print("
Run with --apply to execute these changes")

if __name__ == "__main__":
    main()

Usage

# Preview what would happen (safe)
python3 contact_bot.py

# Actually apply changes
python3 contact_bot.py --apply

# Schedule weekly cleanup
0 6 * * 1 cd /path/to/script && python3 contact_bot.py --apply >> /var/log/contact_bot.log 2>&1

Safety considerations

  • Always dry-run first. Review the output before using --apply. Blocking a legitimate sender is hard to undo (the contact state can be changed back, but bounced emails during that time are lost).
  • Start with high thresholds. Set BLOCK_THRESHOLD high (100+) initially and lower it over time as you gain confidence in the rules.
  • Exclude recent contacts. Add a check for created_at — do not block contacts less than 7 days old, as they may not have had time to establish a pattern.
  • Log everything. Keep a log of every action taken so you can audit and reverse mistakes.

Extending the bot

  • Mute instead of block: For contacts that are noisy but not spam (social media notifications), use state muted instead of blocked. Their email is delivered but marked as read.
  • Category-based rules: Use GET /v1/categories to fetch all 20 categories and create nuanced rules per category.
  • Combine with messages data: Fetch recent messages per contact to check spam scores before deciding. A contact with many emails but consistently low spam scores is probably legitimate.

Ready to take control of your inbox?

Start protecting your email with Cleanbox — free plan available, no credit card required.

Get started free