Inbox lifecycle manager skill
The weekly keep/cancel/promote/buy loop for a cold email sending fleet.
by growthenginenowoslawski·MIT license·★ 736 Stars on the repo·GitHub ↗
npx degit growthenginenowoslawski/coldoutboundskills/skills/inbox-lifecycle-manager#main ~/.claude/skills/inbox-lifecycle-managerChecked ·commit main
Files of Inbox lifecycle manager
Show the full text341 lines
Inbox Lifecycle Manager
Diagnosis tells you a domain is sick. This skill decides whether it dies.
Every cold email fleet leaks money in two directions at once: you keep paying for burned domains that will never reply again, and you run under your send goal because nobody promoted the warmed reserves sitting idle. This is the weekly loop that fixes both, with an approval gate in the middle so nothing gets cancelled on a hunch.
Read-only by default. The plan step writes nothing, anywhere. Only the apply step, run after an explicit human "yes", changes a tag, a campaign, or a provider subscription.
When to use
- The weekly hygiene routine (Friday morning is the natural slot — see
/cold-email-weekly-rhythm) - "Which inboxes should I cancel?" / "What's burned?" / "Clean up my domains"
- "I'm under my daily send goal" / "How many domains do I need to buy?"
- "My inbox bill is too high" — the spend section prices every domain against what it earns
- After
/email-deliverability-auditor/deliverability-incident-responsehands you aSENDER_BURNEDverdict and you need to act on it
Not this skill: why a domain is bad (/email-deliverability-audit), fixing DNS
(/zapmail-domain-setup-public), an acute incident (/deliverability-incident-response),
warmup settings and signatures (/smartlead-inbox-manager).
The status model
Four statuses, and they apply to a whole domain, never to one inbox inside it.
| Status | Meaning | Warmup | In campaigns |
|---|---|---|---|
| Warmup | Newly provisioned, under 14 days of warmup | ON | no |
| Insurance | Warmed, healthy, idle — the reserve pool | ON | no |
| Active | Sending in live campaigns | OFF or minimal | yes |
| Cancel | Condemned; stop sending, cancel the subscription | OFF | no |
⛔ Never split a domain's inboxes across statuses. Mailbox providers judge reputation at the domain level. Half a domain "Active" and half "Cancel" means you are still sending from a domain you have declared burned, and the healthy half inherits the reputation of the dead half. Every decision below is computed per domain and applied to every inbox on it.
⛔ Warmup domains are untouchable. Not counted as capacity, not promotable, not demotable,
not cancellable. If your tool has no Warmup tag, the domain's age is your only protection —
see the age guard below, and keep a list of domains provisioned in the last 30 days.
In Smartlead these statuses are tags. See /smartlead-inbox-manager for the tagging mechanics.
Sample-size floors — the rule that is broken most often
A domain is only judged once there is enough data to judge it. Below the floor the answer is "we don't know yet", and that is a legitimate output, not a failure.
| Judgment | Floor | Why |
|---|---|---|
| Reply rate | 200 sends in the window | At a 1% line, 200 sends is ~2 expected replies. Below 200, a 0% reply rate cannot distinguish a burned domain from an unlucky week. |
| Bounce rate | 50 sends | Below that, one dead address reads as 2% and two read as 4% — straight through a 3% threshold. |
| Bounce composition | 30 classified bounces for that domain | A domain with no sampled bounces has a 0% sender-originated share by construction, which looks identical to a rotten list. |
| Domain age | 30 days | A domain that has not finished ramping has not earned a verdict. |
| Placement test | 100-300 senders per test | Smaller samples swing double digits run to run. |
⛔ Unresolvable age counts as young. If you cannot date a domain, it is TOO_YOUNG and it is
not cancelled. Fail closed: you can always cancel it next week.
The decision ladder
Run top to bottom per domain, over a 7-day window (fall back to 14 days if 7 days is under the 200-send floor). The first gate that matches wins.
G0. Status is Warmup, or any inbox on the domain is in Warmup?
-> EXCLUDED. Not judged. Stop.
G1. Domain age < 30 days, or age unknown?
-> TOO_YOUNG. Never a cancel candidate at any reply rate. Stop.
G2. sent < 200 in the window?
-> INSUFFICIENT_DATA. Do not judge the reply rate. Re-check next week. Stop.
G3. sent >= 50 AND bounce_rate > 3%?
-> BOUNCE_FORK. This is not a cancel decision yet — a high bounce rate is usually the
LIST, not the domain. Hand to /email-deliverability-audit to classify the bounce
codes before you condemn anything. A domain is only cancelled out of this fork when
the bounces are majority sender-originated AND the DNS auth is already clean.
(>20% of classified bounces in the blocklist codes 5.7.606-5.7.614, or DSN text
naming a blocklist, means burned regardless of the bounce rate.)
G4. reply_rate >= 1.0%?
-> KEEP. Healthy enough to stay Active. Stop.
G5. reply_rate < 1.0% (or 0 replies on >= 150 sends), age >= 30d, bounce not the story?
-> CANCEL CANDIDATE. The domain has had a fair sample and did not reply. It is burned.
BUT: only cancel if you have an Insurance domain to swap in, or you are deliberately
shrinking. Otherwise -> KEEP_BELOW_THRESHOLD, flagged, and buy replacements first.
The numbers, and where they come from
| Threshold | Value | Note |
|---|---|---|
| Cancel line (reply) | < 1.0% over 7d at ≥200 sends | The line below which a domain stops earning its subscription. |
| Zero-reply cancel | 0 replies at ≥150 sends | A 0% rate is decisive earlier than a low-but-nonzero one. |
| Healthy | ≥ 1.0% | Keep. |
| Genuinely good | ≥ 1.5% | Full marks. Do not cancel anything near this. |
| Bounce fork / autopause | > 3% at ≥50 sends | Same number your sending tool should use for bounce_autopause_threshold. |
| Blocklist share | > 20% of classified bounces | Burned regardless of bounce rate. |
| Minimum age | 30 days | Hard floor, no exceptions. |
| Placement | ≥85% inbox = healthy, 70-84% degraded, <70% fails | From a real seed placement test, not a guess. |
| Warmup reputation | < 98% = pull the inbox | If the tool reports it live. |
One number, everywhere in this repo: 1%. A domain that cannot reply at 1% after a fair sample is not earning its subscription. The same 1% that flags a domain in
/email-deliverability-auditis the line that cancels it here — the difference between "flag" and "cancel" is not a second threshold, it is the floors: 200 sends and 30 days of age. Below those, 1% means nothing and nothing gets cancelled.
Warmup-off is "held", not "healthy"
An inbox with warmup switched off reports no reputation, so you cannot evaluate it at all. Treat it as HELD: excluded from capacity, not attached to campaigns, reported for a human. Do not silently turn warmup back on to make the number appear — that rewrites the very signal you are measuring. A shortfall caused by holds is a finding to report, never a reason to release the holds.
Capacity and the buy plan
Cancelling without replacing is how you wake up at half your send goal.
goal = your daily send target (emails/day)
inbox capacity = 30/day per Google inbox, 30/day per Outlook inbox, ~15/day per SMTP inbox
domain capacity = 30 x (actual inbox count on that domain) <- never a flat per-domain number
active capacity = sum of domain capacity over Active domains
insurance target = 50% of goal, held warm in reserve
⛔ Count capacity per inbox, never per domain. "150/day per domain" is the single most common capacity error — it assumes 5 inboxes on every domain and silently over-reports a 2-inbox domain by 90/day. Multiply the real inbox count.
Then:
- Promote from Insurance until
active capacity >= goal— oldest domain first. Age is trust; spend the oldest reserve, keep the newest warming. - Demote Active → Insurance when active capacity is ≥ 1.25x goal. Paying to send more than you planned is still paying.
- Buy when
goal + 50% insuranceexceeds what you have after promotion:
capacity_short = (goal * 1.5) - (active + insurance capacity, post-promotion)
inboxes_to_buy = ceil(capacity_short / 30)
domains_to_buy = ceil(inboxes_to_buy / inboxes_per_domain) # typically 3
Express the ask to a human in emails/day of capacity, not in domain count — "we are 900/day short" is a decision; "we need 10 domains" is an implementation detail they cannot sanity-check.
Buy new domains with /zapmail-domain-setup-public, then warm them via /smartlead-inbox-manager.
New domains are not capacity for at least 14 days of warmup plus the 30-day judging age — plan
the buy a month before you need the sends.
Spend math — is this domain worth its subscription?
| Item | Typical |
|---|---|
| Inbox subscription | $2.00-$2.60 / inbox / month |
| Domain registration | ~$10-12 / year (cap what you will pay; cheap TLDs are fine for sending) |
| A 3-inbox domain | ~$6-8 / month, ~90 sends/day, ~2,700 sends/month |
At 1% reply that is ~27 replies/month from a $7 domain. At 0.2% it is ~5. The cancel line is not an aesthetic judgment — it is the point where the subscription stops paying for itself.
Already dead, do not pay to cancel carefully: a domain whose warmup is off and whose SMTP connection fails is not sending anything. Cancel the subscription and move on; it needs no swap-in.
The weekly loop
Step 1 — Pull fresh data (read-only)
npx tsx scripts/plan-lifecycle.ts --goal=2000 --out=./lifecycle-$(date +%F)
⛔ Always pull live. Any cached or mirrored "last 7 days" column in your own database goes stale silently and you will never notice — the plan just quietly starts condemning healthy domains. The API window is the only truth.
The script pulls per-domain sent/replied/bounced for 7d and 14d, joins every inbox (age, tags, warmup state, reputation, SMTP health), applies the ladder above, and writes:
plan.csv— one row per domain, with verdict and every number that forced itactions.csv— only the rows where something should change- a printed summary
It calls no write endpoint. You can run it on someone else's account without risk.
Step 2 — Report
Summarize for the human:
- Totals: cancels / demotes / promotes / buys
- Capacity vs goal, per client or per program, flagging anything under 90% coverage
KEEP_BELOW_THRESHOLDrows — below the line but with no reserve to swap in (this is the "buy first" list)- Watch list: zero-reply domains with 100-199 sends, which will cross the floor next week
- Anything with no goal set or no insurance pool at all
Step 3 — HUMAN APPROVAL GATE
Ask plainly: "Does this plan look right? Should I apply it?" Wait for an explicit yes. Never apply on a maybe.
Ask the second question too, separately: "Tags only, or also cancel the subscriptions at the provider?" These are different blast radii. Tag changes are reversible in seconds; a provider cancellation may not be reversible at all once the billing period lapses.
Ask the third question: "Any domain provisioned recently that should be held back from promotion?" If your tool has no Warmup status, this question is the only thing standing between a two-week-old domain and a live campaign.
Step 4 — Snapshot, then apply
⛔ Snapshot before you apply. Write the current tag/status of every domain the plan touches to a dated file first. Nothing in the apply path records prior state, and without a snapshot there is no clean undo.
npx tsx scripts/plan-lifecycle.ts --goal=2000 --out=./lifecycle-$(date +%F) --snapshot
# review, get the yes, then:
npx tsx scripts/apply-lifecycle.ts --actions=./lifecycle-$(date +%F)/actions.csv --apply
Without --apply the apply script is a dry run: it prints exactly what it would change and exits.
Run the dry run every single time and read the counts before you trust them.
Provider cancellations are not in this script on purpose. Do them deliberately, in batches you can see, after the tag changes have settled. See the cancellation rules below.
Step 5 — Verify (mandatory before reporting success)
A 200 OK is not proof. Read the state back.
- Re-fetch ~10 mutated domains and confirm the tag actually changed.
- Re-run the capacity query and report coverage vs goal. Anything under 90% is an open item.
- If you cancelled at a provider, read the subscription state back from the provider. Never report a cancel batch as successful off the cancel response alone.
Step 6 — Rollback, if you regret it
Reversibility has a clock. Tag changes are instant to undo from the snapshot. A scheduled provider removal is usually reversible only while it is still scheduled — once the period lapses the domain and its reputation are gone. Decide fast, and check the provider's revert path exists before you fire the cancel, not after.
Cancellation rules (the part that costs real money)
- ⛔ Never cancel a domain under 30 days old, at any reply rate.
- ⛔ Never cancel on a partial sample. Under 200 sends the verdict is "unknown".
- ⛔ Never bulk-cancel by pattern match. Cancel an explicit, enumerated list of domain IDs you have read back. A partial-match cancel on a provider API has taken out an order of magnitude more domains than intended; the blast radius of a wrong filter here is your whole fleet.
- ⛔ Never blind-retry a provider cancel. If the response is ambiguous, read the subscription state back before retrying. Retrying a cancel that succeeded has cancelled neighbouring subscriptions on real provider APIs.
- ⛔ Never un-cancel a domain that is replying well just to hit a spend target, and never cancel one just because a domain is idle — idle is what Insurance is for.
- Cancel in whole domains. Partial-domain cancellation leaves you sending from a domain you have condemned.
- Batch small, verify each batch. Ten domains, read back, then the next ten. A cancel run is not the place to discover your filter was wrong.
- Keep the domain registration if the domain is worth re-warming later; cancel only the inbox subscription. Registration is ~$1/month, an inbox is ~$2.50 each.
Failure modes
- Stale mirrored metrics. Any
*_7dcolumn you maintain yourself drifts silently. Pull live. - Flat per-domain capacity. See the capacity section — multiply real inbox counts.
min()over a domain's statuses. If a domain has mixed inbox statuses, aggregate by restrictiveness (Warmup > Cancel > Insurance > Active). Alphabeticalmin()returns "Active" and lets a mostly-warming domain be judged and cancelled.- Judging bounces per client instead of per domain. A client-level bounce split tells you nothing about which domain to cancel. Attribute bounces to the sending domain first.
- Old campaign history polluting the window. Bounce/reply rosters often return a campaign's entire history with no date parameter. Filter client-side on the sent date, or bounces from a completed campaign six months ago will drive this week's cancel list.
- Page size over 100. Most Smartlead endpoints silently return zero rows above
limit=100, which reads as "no data" instead of an error. The domain-wise analytics endpoint is the exception and accepts larger pages. - Judging a domain that is not Active. If an Insurance or Cancel domain shows sends, that is an infrastructure bug to report, not a deliverability verdict.
What to do next
- Cancels approved and applied: buy replacements now, not when you feel the shortfall —
/zapmail-domain-setup-public, then warm for 14 days via/smartlead-inbox-manager. - Plan shows no cancels and you are at goal: nothing to do. Re-run next week.
- Plan is mostly
INSUFFICIENT_DATA: your fleet is bigger than your send volume. Consolidate onto fewer domains before you buy more. - A domain is bad but you don't know why:
/email-deliverability-auditbefore you cancel it — a fixable DNS problem looks exactly like a burned domain in the reply rate.
Related skills
/email-deliverability-audit— why a domain is failing (auth, placement, bounce codes)/deliverability-incident-response— acute triage when something breaks today/smartlead-inbox-manager— tags, warmup, signatures; the mechanics this skill plans/zapmail-domain-setup-public— buying and provisioning the replacements/cold-email-weekly-rhythm— where this loop sits in the week/deliverability-test-public— reply/bounce comparison by inbox type
Scripts
scripts/plan-lifecycle.ts— read-only planner: pulls live metrics, applies the ladder, writesplan.csv+actions.csvscripts/apply-lifecycle.ts— dry-run by default; applies tag changes fromactions.csvon--apply
References
references/decision-ladder.md— the full gate-by-gate tree, verdict table, and what each verdict hands off toreferences/cancellation-safety.md— provider cancellation procedure, batching, readback, rollback windows
| 1 | |
| 2 | name inbox-lifecycle-manager |
| 3 | description The weekly keep/cancel/promote/buy loop for a cold email sending fleet. Decides which domains are burned and should be cancelled, which warmed reserves to promote into campaigns, which capacity to demote, and how many new domains to buy — using measured reply, bounce, age and send-volume floors instead of gut feel. Use when asked "which inboxes should I cancel", "what should I replace this week", "my fleet costs too much", "I'm under my send goal", or as the Friday hygiene routine. |
| 4 | |
| 5 | |
| 6 | # Inbox Lifecycle Manager |
| 7 | |
| 8 | **Diagnosis tells you a domain is sick. This skill decides whether it dies.** |
| 9 | |
| 10 | Every cold email fleet leaks money in two directions at once: you keep paying for burned domains |
| 11 | that will never reply again, and you run under your send goal because nobody promoted the warmed |
| 12 | reserves sitting idle. This is the weekly loop that fixes both, with an approval gate in the |
| 13 | middle so nothing gets cancelled on a hunch. |
| 14 | |
| 15 | **Read-only by default.** The plan step writes nothing, anywhere. Only the apply step, run after |
| 16 | an explicit human "yes", changes a tag, a campaign, or a provider subscription. |
| 17 | |
| 18 | ## When to use |
| 19 | |
| 20 | The weekly hygiene routine (Friday morning is the natural slot — see `/cold-email-weekly-rhythm`) |
| 21 | "Which inboxes should I cancel?" / "What's burned?" / "Clean up my domains" |
| 22 | "I'm under my daily send goal" / "How many domains do I need to buy?" |
| 23 | "My inbox bill is too high" — the spend section prices every domain against what it earns |
| 24 | After `/email-deliverability-audit` or `/deliverability-incident-response` hands you a |
| 25 | `SENDER_BURNED` verdict and you need to act on it |
| 26 | |
| 27 | **Not this skill:** *why* a domain is bad (`/email-deliverability-audit`), fixing DNS |
| 28 | (`/zapmail-domain-setup-public`), an acute incident (`/deliverability-incident-response`), |
| 29 | warmup settings and signatures (`/smartlead-inbox-manager`). |
| 30 | |
| 31 | |
| 32 | |
| 33 | ## The status model |
| 34 | |
| 35 | Four statuses, and they apply to a **whole domain**, never to one inbox inside it. |
| 36 | |
| 37 | | Status | Meaning | Warmup | In campaigns | |
| 38 | |---|---|---|---| |
| 39 | | **Warmup** | Newly provisioned, under 14 days of warmup | ON | no | |
| 40 | | **Insurance** | Warmed, healthy, idle — the reserve pool | ON | no | |
| 41 | | **Active** | Sending in live campaigns | OFF or minimal | yes | |
| 42 | | **Cancel** | Condemned; stop sending, cancel the subscription | OFF | no | |
| 43 | |
| 44 | ⛔ **Never split a domain's inboxes across statuses.** Mailbox providers judge reputation at the |
| 45 | domain level. Half a domain "Active" and half "Cancel" means you are still sending from a domain |
| 46 | you have declared burned, and the healthy half inherits the reputation of the dead half. Every |
| 47 | decision below is computed per domain and applied to every inbox on it. |
| 48 | |
| 49 | ⛔ **Warmup domains are untouchable.** Not counted as capacity, not promotable, not demotable, |
| 50 | not cancellable. If your tool has no `Warmup` tag, the domain's age is your only protection — |
| 51 | see the age guard below, and keep a list of domains provisioned in the last 30 days. |
| 52 | |
| 53 | In Smartlead these statuses are tags. See `/smartlead-inbox-manager` for the tagging mechanics. |
| 54 | |
| 55 | |
| 56 | |
| 57 | ## Sample-size floors — the rule that is broken most often |
| 58 | |
| 59 | A domain is only judged once there is enough data to judge it. Below the floor the answer is |
| 60 | **"we don't know yet"**, and that is a legitimate output, not a failure. |
| 61 | |
| 62 | | Judgment | Floor | Why | |
| 63 | |---|---|---| |
| 64 | | Reply rate | **200 sends** in the window | At a 1% line, 200 sends is ~2 expected replies. Below 200, a 0% reply rate cannot distinguish a burned domain from an unlucky week. | |
| 65 | | Bounce rate | **50 sends** | Below that, one dead address reads as 2% and two read as 4% — straight through a 3% threshold. | |
| 66 | | Bounce *composition* | **30 classified bounces for that domain** | A domain with no sampled bounces has a 0% sender-originated share by construction, which looks identical to a rotten list. | |
| 67 | | Domain age | **30 days** | A domain that has not finished ramping has not earned a verdict. | |
| 68 | | Placement test | 100-300 senders per test | Smaller samples swing double digits run to run. | |
| 69 | |
| 70 | ⛔ **Unresolvable age counts as young.** If you cannot date a domain, it is `TOO_YOUNG` and it is |
| 71 | not cancelled. Fail closed: you can always cancel it next week. |
| 72 | |
| 73 | |
| 74 | |
| 75 | ## The decision ladder |
| 76 | |
| 77 | Run top to bottom per domain, over a **7-day window** (fall back to 14 days if 7 days is under the |
| 78 | 200-send floor). The first gate that matches wins. |
| 79 | |
| 80 | |
| 81 | G0. Status is Warmup, or any inbox on the domain is in Warmup? |
| 82 | -> EXCLUDED. Not judged. Stop. |
| 83 | |
| 84 | G1. Domain age < 30 days, or age unknown? |
| 85 | -> TOO_YOUNG. Never a cancel candidate at any reply rate. Stop. |
| 86 | |
| 87 | G2. sent < 200 in the window? |
| 88 | -> INSUFFICIENT_DATA. Do not judge the reply rate. Re-check next week. Stop. |
| 89 | |
| 90 | G3. sent >= 50 AND bounce_rate > 3%? |
| 91 | -> BOUNCE_FORK. This is not a cancel decision yet — a high bounce rate is usually the |
| 92 | LIST, not the domain. Hand to /email-deliverability-audit to classify the bounce |
| 93 | codes before you condemn anything. A domain is only cancelled out of this fork when |
| 94 | the bounces are majority sender-originated AND the DNS auth is already clean. |
| 95 | (>20% of classified bounces in the blocklist codes 5.7.606-5.7.614, or DSN text |
| 96 | naming a blocklist, means burned regardless of the bounce rate.) |
| 97 | |
| 98 | G4. reply_rate >= 1.0%? |
| 99 | -> KEEP. Healthy enough to stay Active. Stop. |
| 100 | |
| 101 | G5. reply_rate < 1.0% (or 0 replies on >= 150 sends), age >= 30d, bounce not the story? |
| 102 | -> CANCEL CANDIDATE. The domain has had a fair sample and did not reply. It is burned. |
| 103 | BUT: only cancel if you have an Insurance domain to swap in, or you are deliberately |
| 104 | shrinking. Otherwise -> KEEP_BELOW_THRESHOLD, flagged, and buy replacements first. |
| 105 | |
| 106 | |
| 107 | ### The numbers, and where they come from |
| 108 | |
| 109 | | Threshold | Value | Note | |
| 110 | |---|---|---| |
| 111 | | Cancel line (reply) | **< 1.0%** over 7d at ≥200 sends | The line below which a domain stops earning its subscription. | |
| 112 | | Zero-reply cancel | **0 replies at ≥150 sends** | A 0% rate is decisive earlier than a low-but-nonzero one. | |
| 113 | | Healthy | **≥ 1.0%** | Keep. | |
| 114 | | Genuinely good | **≥ 1.5%** | Full marks. Do not cancel anything near this. | |
| 115 | | Bounce fork / autopause | **> 3%** at ≥50 sends | Same number your sending tool should use for `bounce_autopause_threshold`. | |
| 116 | | Blocklist share | **> 20%** of classified bounces | Burned regardless of bounce rate. | |
| 117 | | Minimum age | **30 days** | Hard floor, no exceptions. | |
| 118 | | Placement | **≥85% inbox = healthy**, 70-84% degraded, **<70% fails** | From a real seed placement test, not a guess. | |
| 119 | | Warmup reputation | **< 98% = pull the inbox** | If the tool reports it live. | |
| 120 | |
| 121 | > **One number, everywhere in this repo: 1%.** A domain that cannot reply at 1% after a fair |
| 122 | > sample is not earning its subscription. The same 1% that flags a domain in |
| 123 | > `/email-deliverability-audit` is the line that cancels it here — the difference between "flag" |
| 124 | > and "cancel" is not a second threshold, it is the **floors**: 200 sends and 30 days of age. |
| 125 | > Below those, 1% means nothing and nothing gets cancelled. |
| 126 | |
| 127 | ### Warmup-off is "held", not "healthy" |
| 128 | |
| 129 | An inbox with warmup switched off reports no reputation, so you cannot evaluate it at all. Treat |
| 130 | it as **HELD**: excluded from capacity, not attached to campaigns, reported for a human. Do not |
| 131 | silently turn warmup back on to make the number appear — that rewrites the very signal you are |
| 132 | measuring. A shortfall caused by holds is a finding to report, never a reason to release the holds. |
| 133 | |
| 134 | |
| 135 | |
| 136 | ## Capacity and the buy plan |
| 137 | |
| 138 | Cancelling without replacing is how you wake up at half your send goal. |
| 139 | |
| 140 | |
| 141 | goal = your daily send target (emails/day) |
| 142 | inbox capacity = 30/day per Google inbox, 30/day per Outlook inbox, ~15/day per SMTP inbox |
| 143 | domain capacity = 30 x (actual inbox count on that domain) <- never a flat per-domain number |
| 144 | active capacity = sum of domain capacity over Active domains |
| 145 | insurance target = 50% of goal, held warm in reserve |
| 146 | |
| 147 | |
| 148 | ⛔ **Count capacity per inbox, never per domain.** "150/day per domain" is the single most common |
| 149 | capacity error — it assumes 5 inboxes on every domain and silently over-reports a 2-inbox domain |
| 150 | by 90/day. Multiply the real inbox count. |
| 151 | |
| 152 | Then: |
| 153 | |
| 154 | **Promote** from Insurance until `active capacity >= goal` — **oldest domain first**. Age is |
| 155 | trust; spend the oldest reserve, keep the newest warming. |
| 156 | **Demote** Active → Insurance when active capacity is **≥ 1.25x goal**. Paying to send more |
| 157 | than you planned is still paying. |
| 158 | **Buy** when `goal + 50% insurance` exceeds what you have after promotion: |
| 159 | |
| 160 | |
| 161 | capacity_short = (goal * 1.5) - (active + insurance capacity, post-promotion) |
| 162 | inboxes_to_buy = ceil(capacity_short / 30) |
| 163 | domains_to_buy = ceil(inboxes_to_buy / inboxes_per_domain) # typically 3 |
| 164 | |
| 165 | |
| 166 | Express the ask to a human in **emails/day of capacity**, not in domain count — "we are 900/day |
| 167 | short" is a decision; "we need 10 domains" is an implementation detail they cannot sanity-check. |
| 168 | |
| 169 | Buy new domains with `/zapmail-domain-setup-public`, then warm them via `/smartlead-inbox-manager`. |
| 170 | New domains are not capacity for **at least 14 days of warmup plus the 30-day judging age** — plan |
| 171 | the buy a month before you need the sends. |
| 172 | |
| 173 | |
| 174 | |
| 175 | ## Spend math — is this domain worth its subscription? |
| 176 | |
| 177 | | Item | Typical | |
| 178 | |---|---| |
| 179 | | Inbox subscription | $2.00-$2.60 / inbox / month | |
| 180 | | Domain registration | ~$10-12 / year (cap what you will pay; cheap TLDs are fine for sending) | |
| 181 | | A 3-inbox domain | ~$6-8 / month, ~90 sends/day, ~2,700 sends/month | |
| 182 | |
| 183 | At 1% reply that is ~27 replies/month from a $7 domain. At 0.2% it is ~5. The cancel line is not |
| 184 | an aesthetic judgment — it is the point where the subscription stops paying for itself. |
| 185 | |
| 186 | **Already dead, do not pay to cancel carefully:** a domain whose warmup is off *and* whose SMTP |
| 187 | connection fails is not sending anything. Cancel the subscription and move on; it needs no swap-in. |
| 188 | |
| 189 | |
| 190 | |
| 191 | ## The weekly loop |
| 192 | |
| 193 | ### Step 1 — Pull fresh data (read-only) |
| 194 | |
| 195 | |
| 196 | npx tsx scripts/plan-lifecycle.ts --goal=2000 --out=./lifecycle-$(date +%F) |
| 197 | |
| 198 | |
| 199 | ⛔ **Always pull live.** Any cached or mirrored "last 7 days" column in your own database goes |
| 200 | stale silently and you will never notice — the plan just quietly starts condemning healthy |
| 201 | domains. The API window is the only truth. |
| 202 | |
| 203 | The script pulls per-domain sent/replied/bounced for 7d and 14d, joins every inbox (age, tags, |
| 204 | warmup state, reputation, SMTP health), applies the ladder above, and writes: |
| 205 | |
| 206 | `plan.csv` — one row per domain, with verdict and every number that forced it |
| 207 | `actions.csv` — only the rows where something should change |
| 208 | a printed summary |
| 209 | |
| 210 | It calls no write endpoint. You can run it on someone else's account without risk. |
| 211 | |
| 212 | ### Step 2 — Report |
| 213 | |
| 214 | Summarize for the human: |
| 215 | |
| 216 | Totals: cancels / demotes / promotes / buys |
| 217 | Capacity vs goal, **per client or per program**, flagging anything under 90% coverage |
| 218 | `KEEP_BELOW_THRESHOLD` rows — below the line but with no reserve to swap in (this is the "buy |
| 219 | first" list) |
| 220 | Watch list: zero-reply domains with 100-199 sends, which will cross the floor next week |
| 221 | Anything with no goal set or no insurance pool at all |
| 222 | |
| 223 | ### Step 3 — HUMAN APPROVAL GATE |
| 224 | |
| 225 | Ask plainly: *"Does this plan look right? Should I apply it?"* Wait for an explicit yes. Never |
| 226 | apply on a maybe. |
| 227 | |
| 228 | Ask the second question too, separately: **"Tags only, or also cancel the subscriptions at the |
| 229 | provider?"** These are different blast radii. Tag changes are reversible in seconds; a provider |
| 230 | cancellation may not be reversible at all once the billing period lapses. |
| 231 | |
| 232 | Ask the third question: **"Any domain provisioned recently that should be held back from |
| 233 | promotion?"** If your tool has no Warmup status, this question is the only thing standing between |
| 234 | a two-week-old domain and a live campaign. |
| 235 | |
| 236 | ### Step 4 — Snapshot, then apply |
| 237 | |
| 238 | ⛔ **Snapshot before you apply.** Write the current tag/status of every domain the plan touches to |
| 239 | a dated file first. Nothing in the apply path records prior state, and without a snapshot there is |
| 240 | no clean undo. |
| 241 | |
| 242 | |
| 243 | npx tsx scripts/plan-lifecycle.ts --goal=2000 --out=./lifecycle-$(date +%F) --snapshot |
| 244 | # review, get the yes, then: |
| 245 | npx tsx scripts/apply-lifecycle.ts --actions=./lifecycle-$(date +%F)/actions.csv --apply |
| 246 | |
| 247 | |
| 248 | Without `--apply` the apply script is a dry run: it prints exactly what it would change and exits. |
| 249 | Run the dry run every single time and read the counts before you trust them. |
| 250 | |
| 251 | Provider cancellations are **not** in this script on purpose. Do them deliberately, in batches you |
| 252 | can see, after the tag changes have settled. See the cancellation rules below. |
| 253 | |
| 254 | ### Step 5 — Verify (mandatory before reporting success) |
| 255 | |
| 256 | A `200 OK` is not proof. Read the state back. |
| 257 | |
| 258 | Re-fetch ~10 mutated domains and confirm the tag actually changed. |
| 259 | Re-run the capacity query and report coverage vs goal. Anything under 90% is an open item. |
| 260 | If you cancelled at a provider, read the subscription state back from the provider. Never |
| 261 | report a cancel batch as successful off the cancel response alone. |
| 262 | |
| 263 | ### Step 6 — Rollback, if you regret it |
| 264 | |
| 265 | Reversibility has a clock. Tag changes are instant to undo from the snapshot. A scheduled provider |
| 266 | removal is usually reversible **only while it is still scheduled** — once the period lapses the |
| 267 | domain and its reputation are gone. Decide fast, and check the provider's revert path exists |
| 268 | *before* you fire the cancel, not after. |
| 269 | |
| 270 | |
| 271 | |
| 272 | ## Cancellation rules (the part that costs real money) |
| 273 | |
| 274 | ⛔ **Never cancel a domain under 30 days old**, at any reply rate. |
| 275 | ⛔ **Never cancel on a partial sample.** Under 200 sends the verdict is "unknown". |
| 276 | ⛔ **Never bulk-cancel by pattern match.** Cancel an explicit, enumerated list of domain IDs you |
| 277 | have read back. A partial-match cancel on a provider API has taken out an order of magnitude |
| 278 | more domains than intended; the blast radius of a wrong filter here is your whole fleet. |
| 279 | ⛔ **Never blind-retry a provider cancel.** If the response is ambiguous, read the subscription |
| 280 | state back before retrying. Retrying a cancel that succeeded has cancelled neighbouring |
| 281 | subscriptions on real provider APIs. |
| 282 | ⛔ **Never un-cancel a domain that is replying well** just to hit a spend target, and never |
| 283 | cancel one just because a domain is idle — idle is what Insurance is for. |
| 284 | **Cancel in whole domains.** Partial-domain cancellation leaves you sending from a domain you |
| 285 | have condemned. |
| 286 | **Batch small, verify each batch.** Ten domains, read back, then the next ten. A cancel run is |
| 287 | not the place to discover your filter was wrong. |
| 288 | **Keep the domain registration** if the domain is worth re-warming later; cancel only the inbox |
| 289 | subscription. Registration is ~$1/month, an inbox is ~$2.50 each. |
| 290 | |
| 291 | |
| 292 | |
| 293 | ## Failure modes |
| 294 | |
| 295 | **Stale mirrored metrics.** Any `*_7d` column you maintain yourself drifts silently. Pull live. |
| 296 | **Flat per-domain capacity.** See the capacity section — multiply real inbox counts. |
| 297 | **`min()` over a domain's statuses.** If a domain has mixed inbox statuses, aggregate by |
| 298 | *restrictiveness* (Warmup > Cancel > Insurance > Active). Alphabetical `min()` returns "Active" |
| 299 | and lets a mostly-warming domain be judged and cancelled. |
| 300 | **Judging bounces per client instead of per domain.** A client-level bounce split tells you |
| 301 | nothing about which domain to cancel. Attribute bounces to the sending domain first. |
| 302 | **Old campaign history polluting the window.** Bounce/reply rosters often return a campaign's |
| 303 | *entire* history with no date parameter. Filter client-side on the sent date, or bounces from a |
| 304 | completed campaign six months ago will drive this week's cancel list. |
| 305 | **Page size over 100.** Most Smartlead endpoints silently return zero rows above `limit=100`, |
| 306 | which reads as "no data" instead of an error. The domain-wise analytics endpoint is the |
| 307 | exception and accepts larger pages. |
| 308 | **Judging a domain that is not Active.** If an Insurance or Cancel domain shows sends, that is an |
| 309 | infrastructure bug to report, not a deliverability verdict. |
| 310 | |
| 311 | |
| 312 | |
| 313 | ## What to do next |
| 314 | |
| 315 | **Cancels approved and applied:** buy replacements now, not when you feel the shortfall — |
| 316 | `/zapmail-domain-setup-public`, then warm for 14 days via `/smartlead-inbox-manager`. |
| 317 | **Plan shows no cancels and you are at goal:** nothing to do. Re-run next week. |
| 318 | **Plan is mostly `INSUFFICIENT_DATA`:** your fleet is bigger than your send volume. Consolidate |
| 319 | onto fewer domains before you buy more. |
| 320 | **A domain is bad but you don't know why:** `/email-deliverability-audit` before you cancel it — |
| 321 | a fixable DNS problem looks exactly like a burned domain in the reply rate. |
| 322 | |
| 323 | ## Related skills |
| 324 | |
| 325 | `/email-deliverability-audit` — *why* a domain is failing (auth, placement, bounce codes) |
| 326 | `/deliverability-incident-response` — acute triage when something breaks today |
| 327 | `/smartlead-inbox-manager` — tags, warmup, signatures; the mechanics this skill plans |
| 328 | `/zapmail-domain-setup-public` — buying and provisioning the replacements |
| 329 | `/cold-email-weekly-rhythm` — where this loop sits in the week |
| 330 | `/deliverability-test-public` — reply/bounce comparison by inbox type |
| 331 | |
| 332 | ## Scripts |
| 333 | |
| 334 | `scripts/plan-lifecycle.ts` — read-only planner: pulls live metrics, applies the ladder, writes `plan.csv` + `actions.csv` |
| 335 | `scripts/apply-lifecycle.ts` — dry-run by default; applies tag changes from `actions.csv` on `--apply` |
| 336 | |
| 337 | ## References |
| 338 | |
| 339 | `references/decision-ladder.md` — the full gate-by-gate tree, verdict table, and what each verdict hands off to |
| 340 | `references/cancellation-safety.md` — provider cancellation procedure, batching, readback, rollback windows |
| 341 |
Discussion
Alternatives
Browse more free Claude skills or everything in Marketing.