cold-email-deliverability
Sets up and protects cold-outbound sending infrastructure - dedicated lookalike domains, SPF/DKIM/DMARC authentication, 2-4 week mailbox warm-up, 30-50 sends/day per-mailbox caps, and continuous bounce/complaint monitoring, with a runnable DNS audit script. Use when someone says "my emails are going to spam", "set up cold email infrastructure", "warm up a new domain", "configure SPF DKIM DMARC", "how many cold emails can I send per day", or "my domain got blacklisted". Do NOT use for protecting an opted-in marketing or transactional program on your primary domain - use email-deliverability instead; do NOT use for writing the email copy - use cold-email-craft instead; do NOT use for designing the multi-step cadence - use outreach-sequence-designer instead; do NOT use for sizing the list - use prospect-list-builder instead.
- License 许可
- MIT
- 最近更新
- 2026-10-14
- Stars
- ★ 25
- Forks
- 3
01 前言 Front Matter
- name
- cold-email-deliverability
- metadata
- { "title": "Cold Email Deliverability" }
- description
- Sets up and protects cold-outbound sending infrastructure - dedicated lookalike domains, SPF/DKIM/DMARC authentication, 2-4 week mailbox warm-up, 30-50 sends/day per-mailbox caps, and continuous bounce/complaint monitoring, with a runnable DNS audit script. Use when someone says "my emails are going to spam", "set up cold email infrastructure", "warm up a new domain", "configure SPF DKIM DMARC", "how many cold emails can I send per day", or "my domain got blacklisted". Do NOT use for protecting an opted-in marketing or transactional program on your primary domain - use email-deliverability instead; do NOT use for writing the email copy - use cold-email-craft instead; do NOT use for designing the multi-step cadence - use outreach-sequence-designer instead; do NOT use for sizing the list - use prospect-list-builder instead.
03 SKILL.md 正文
Cold Email Deliverability
Deliverability is not a copywriting problem; it is an infrastructure and reputation problem. The single most expensive mistake in cold outbound is sending it from the primary corporate domain - one spam-trap hit or complaint spike and invoices, hiring emails, and the founder's replies all start landing in spam, and that damage is slow and painful to reverse. Treat cold sending as a blast radius to isolate on purpose: separate domains, separate mailboxes, conservative volume, relentless monitoring.
Mailbox providers (Gmail, Outlook) score the sending identity - domain + IP + behavioral signals like complaints, bounces, and engagement - not the cleverness of the subject line. Primary-inbox placement is earned by behaving like a real human correspondent at low volume. Good copy on a burned domain still lands in spam. Note the scope: cold outreach to people who never opted in. An opted-in newsletter or transactional program follows different rules (subdomains of the primary domain, higher warm-up ramps, sunset policies) - that is email-deliverability territory.
Inputs to collect
- Target daily send volume (or monthly prospect goal ÷ ~20 business days).
- Primary corporate domain, and confirmation that it will NOT be used for cold sends.
- Existing sending domains, their age, and any prior reputation damage (blacklist history, complaint spikes).
- ESP/mailbox provider (Google Workspace, Microsoft 365) and any existing SPF includes on the sending domains.
- List source and whether addresses are verified. If unverified, stop - route through lead-enrichment first.
- Label any volume or list-quality figure the user estimates rather than measures as a guess.
The workflow
-
Isolate the blast radius - separate sending domains. Never send cold from the primary domain (acme.com). Register lookalike domains (trygetacme.com, acme-team.com, getacme.io) dedicated to outbound. Redirect them (301) to the main site so they resolve. If one gets burned, pause and rotate it without touching corporate mail.
-
Authenticate DNS - all three, or you are getting filtered.
- SPF (TXT record): authorizes which servers may send for the domain. Publish exactly one SPF record; multiple SPF TXT records = permerror = fail. Stay under the 10 DNS-lookup limit (each include: counts; nested includes count too). Example:
v=spf1 include:_spf.google.com ~all. - DKIM (TXT at a selector subdomain, e.g. selector._domainkey.domain): cryptographically signs each message so the receiver verifies it was not altered and the domain authorized it. Use a per-domain 2048-bit key (1024 is legacy/weak).
- DMARC (TXT at _dmarc.domain): tells receivers what to do when SPF/DKIM fail and requires alignment (visible From domain matches the authenticated domain). Start at
p=noneto monitor via aggregate reports, then tighten top=quarantineand eventuallyp=rejectonce legitimate mail passes cleanly. Example:v=DMARC1; p=none; rua=mailto:dmarc@domain; adkim=s; aspf=s. - Together: SPF says "this server is allowed," DKIM says "this message is intact and authorized," DMARC ties them to the visible From and sets enforcement. Gmail/Yahoo bulk-sender rules effectively require all three.
- SPF (TXT record): authorizes which servers may send for the domain. Publish exactly one SPF record; multiple SPF TXT records = permerror = fail. Stay under the 10 DNS-lookup limit (each include: counts; nested includes count too). Example:
-
Age and warm before sending a single real prospect. Brand-new domains have zero reputation and get filtered hard. Register at least 2 weeks before launch, then warm each mailbox for 2-4 weeks: start at ~5 sends/day and ramp gradually toward the cap, mixed with received and replied mail (warmup tools or a manual network) so providers see natural two-way engagement before volume climbs.
-
Cap per-mailbox volume and scale horizontally. Treat ~30-50 cold sends/day/mailbox as the ceiling, not a target. To send more, add mailboxes (2-3 per domain) and rotate across a pool. Size the list to capacity: 5 mailboxes × 40/day ≈ 200 new prospects/day. Sequencing volume against list size is prospect-list-builder territory.
-
List hygiene - verify before sending. Every address goes through verification first (lead-enrichment). Remove invalids, role accounts (info@, sales@), and risky catch-all domains, or sample them carefully. Bounce rate is the fastest way to torch a new domain - keep it under 2-3%. A dirty list burns warm infrastructure in a single batch.
-
Content hygiene - look like a person, not a campaign. Plain text beats heavy HTML. No images and at most one link in the first touch (links are the top silent spam trigger). Avoid spam-trigger phrasing ("free", "guarantee", "act now", excessive caps/exclamation). Use a custom tracking domain if tracking opens, or turn open-tracking off - shared tracking domains are widely blacklisted. Include a real, plain-text opt-out.
-
Monitor the signals providers actually measure. Watch bounce rate (<2-3%), spam-complaint rate (keep well under 0.1% - the red line; Gmail enforces at 0.3%), and reply rate as a health proxy (a sudden drop usually means placement collapsed, not worse copy). Run seed/inbox-placement tests across Gmail/Outlook/Yahoo before and during campaigns. Enroll every sending domain in Google Postmaster Tools for real reputation and spam-rate data.
-
When a domain gets burned: pause, diagnose, rotate. Stop sending from it immediately. Check blacklists (MXToolbox), Postmaster reputation, recent bounce/complaint spikes, and whether DNS auth silently broke. Light damage: drop volume to near-zero and re-warm for weeks. Heavy damage (blacklisted, complaint spike): retire the domain, rotate to a fresh warmed one, and fix the upstream cause - bad list or aggressive volume - before reusing the pattern.
Pre-flight checklist
INFRASTRUCTURE
[ ] Sending domain(s) SEPARATE from primary corporate domain
[ ] Each sending domain 301-redirects to the main site and resolves
[ ] Domain registered >= 2 weeks ago (aged, not same-day)
AUTHENTICATION (verify with the audit script below)
[ ] Exactly ONE SPF TXT record, ends in ~all or -all, under 10 DNS lookups
[ ] DKIM published at selector._domainkey, 2048-bit key, signs outbound
[ ] DMARC at _dmarc, p=none to start, rua= reporting address set
[ ] SPF + DKIM align with the visible From domain (DMARC passes)
WARMUP & VOLUME
[ ] Each mailbox warmed 2-4 weeks, volume ramped gradually from ~5/day
[ ] Per-mailbox daily cap set (<= 30-50 cold sends/day)
[ ] Mailbox pool + rotation configured; list sized to total capacity
LIST & CONTENT
[ ] Every address verified; invalids/role/catch-all removed
[ ] Projected bounce rate < 2-3%
[ ] First touch: plain text, <= 1 link, no images, no spam-trigger words
[ ] Custom tracking domain configured, or open-tracking OFF
[ ] Real plain-text unsubscribe / opt-out present
[ ] Legal basis checked per recipient country (not legal advice; confirm with counsel):
US CAN-SPAM - accurate From/subject, a valid physical postal address, opt-outs
honored within 10 business days. EU/UK - GDPR lawful basis (usually a documented
legitimate-interest assessment) plus national e-privacy rules, which vary for B2B
email (some countries require prior consent). Canada CASL - consent (express or
implied) required before sending.
MONITORING
[ ] Google Postmaster Tools enrolled for each sending domain
[ ] Seed/inbox-placement test passed (primary, not spam/Promotions)
[ ] Dashboards for bounce %, complaint %, reply % with alert thresholds
Worked example: infrastructure for 200 cold sends/day
Goal: reach ~4,000 net-new prospects/month (~200/business day).
Capacity math: 200/day ÷ 40 cold sends/mailbox/day = 5 mailboxes needed.
Domains: 2 sending domains, 3 mailboxes each = 6 mailboxes (1 spare).
trygetacme.com -> ava@, ava.chen@, hello@ (301 -> acme.com)
acme-team.com -> ava@, ava.chen@, hello@ (301 -> acme.com)
DNS per domain: SPF TXT @ v=spf1 include:_spf.google.com ~all
DKIM TXT google._domainkey (2048-bit, from Google Admin)
DMARC TXT _dmarc v=DMARC1; p=none; rua=mailto:dmarc@acme.com; adkim=s; aspf=s
Timeline: Wk 0 register + DNS + redirect. Wk 1-3 warm all 6 mailboxes
(start ~5/day, ramp to ~40). Wk 4 begin real sends at full cap.
Real volume: 5 active mailboxes × 40 = 200/day. Hold 1 mailbox in reserve.
List sizing: queue ~200 verified prospects/day; bounce budget < 6/day (3%).
Guardrails: complaint-rate alert at 0.1%; if any domain spikes, pause it,
shift load to the spare, re-warm, diagnose before resuming.
Authentication audit script (Node, zero deps)
Run before launch and on a schedule: node check-deliverability.mjs trygetacme.com [dkim-selector]
#!/usr/bin/env node
// check-deliverability.mjs - sanity-check SPF/DKIM/DMARC for a sending domain.
// Usage: node check-deliverability.mjs <domain> [dkimSelector=google]
import { resolveTxt } from 'node:dns/promises';
const domain = process.argv[2];
const selector = process.argv[3] || 'google';
if (!domain) { console.error('usage: node check-deliverability.mjs <domain> [dkimSelector]'); process.exit(2); }
const out = [];
const log = (level, label, msg) => out.push({ level, label, msg });
async function txt(name) {
try {
// resolveTxt returns string[][]; join each record's chunks (DNS 255-char splits).
return (await resolveTxt(name)).map(chunks => chunks.join(''));
} catch (e) {
if (e.code === 'ENOTFOUND' || e.code === 'ENODATA') return [];
throw e;
}
}
// Count SPF DNS lookups (include/a/mx/ptr/exists and redirect=), following
// include: and redirect= targets. `all`, `ip4:` and `ip6:` cost no lookup.
async function countSpfLookups(record, seen = new Set(), depth = 0) {
if (depth > 5) return 0; // guard runaway recursion
let count = 0;
for (const term of record.split(/\s+/)) {
// Optional qualifier (+ - ~ ?), then a mechanism name that ends the term or
// is followed by ':' or '/', so a bare `all` never matches the `a` mechanism.
const m =
term.match(/^[+\-~?]?(include|a|mx|ptr|exists)(?=$|[:\/])(?::([^\/\s]+))?/i) ||
term.match(/^(redirect)=(\S+)$/i);
if (!m) continue;
count++;
const kind = m[1].toLowerCase();
const target = m[2];
if ((kind === 'include' || kind === 'redirect') && target && !seen.has(target)) {
seen.add(target);
const [nested] = (await txt(target)).filter(r => /^v=spf1/i.test(r));
if (nested) count += await countSpfLookups(nested, seen, depth + 1);
}
}
return count;
}
async function checkSpf() {
const spf = (await txt(domain)).filter(r => /^v=spf1/i.test(r));
if (spf.length === 0) return log('FAIL', 'SPF', 'no v=spf1 record found');
if (spf.length > 1) return log('FAIL', 'SPF', `${spf.length} SPF records (must be exactly 1 - multiple = permerror)`);
const rec = spf[0];
if (!/[~\-]all\s*$/.test(rec)) log('WARN', 'SPF', 'does not end in ~all or -all (open policy)');
const lookups = await countSpfLookups(rec);
if (lookups > 10) log('FAIL', 'SPF', `${lookups} DNS lookups (limit is 10 - exceeding = permerror)`);
else log('PASS', 'SPF', `1 record, ${lookups} lookups, ${(/-all/.test(rec) ? 'hard' : 'soft')} fail policy`);
}
async function checkDkim() {
const name = `${selector}._domainkey.${domain}`;
const recs = (await txt(name)).filter(r => /v=DKIM1|p=/i.test(r));
if (recs.length === 0) return log('FAIL', 'DKIM', `no record at ${name} (try the right selector)`);
const rec = recs[0];
const p = (rec.match(/p=([A-Za-z0-9+/=]*)/) || [])[1] || '';
if (p === '') return log('FAIL', 'DKIM', 'key revoked/empty (p=)');
// RSA 2048-bit public key in base64 DER is ~392 chars; 1024-bit is ~216.
const strength = p.length > 300 ? '~2048-bit' : '~1024-bit (weak - upgrade)';
log(p.length > 300 ? 'PASS' : 'WARN', 'DKIM', `key present at selector "${selector}", ${strength}`);
}
async function checkDmarc() {
const recs = (await txt(`_dmarc.${domain}`)).filter(r => /^v=DMARC1/i.test(r));
if (recs.length === 0) return log('FAIL', 'DMARC', `no record at _dmarc.${domain}`);
const rec = recs[0];
const policy = (rec.match(/\bp=(none|quarantine|reject)\b/) || [])[1];
const hasRua = /rua=mailto:/i.test(rec);
if (!policy) return log('FAIL', 'DMARC', 'no p= policy tag');
if (!hasRua) log('WARN', 'DMARC', `p=${policy} but no rua= (you get no aggregate reports)`);
else log(policy === 'none' ? 'WARN' : 'PASS', 'DMARC',
`p=${policy}${policy === 'none' ? ' (monitor-only - tighten toward quarantine/reject)' : ''}, reporting on`);
}
await Promise.all([checkSpf(), checkDkim(), checkDmarc()]);
const order = { FAIL: 0, WARN: 1, PASS: 2 };
out.sort((a, b) => order[a.level] - order[b.level]);
for (const r of out) console.log(`[${r.level.padEnd(4)}] ${r.label.padEnd(5)} ${r.msg}`);
const fails = out.filter(r => r.level === 'FAIL').length;
console.log(`\n${fails ? `${fails} blocking issue(s) - fix before sending.` : 'No blocking auth issues.'}`);
process.exit(fails ? 1 : 0);
Expected output on a healthy domain:
[PASS] SPF 1 record, 3 lookups, soft fail policy
[PASS] DKIM key present at selector "google", ~2048-bit
[WARN] DMARC p=none (monitor-only - tighten toward quarantine/reject), reporting on
No blocking auth issues.
Deliverable
Produce a launch-ready infrastructure plan: the domain and mailbox pool sized to the target volume with the capacity math shown, the three DNS records per domain, a week-by-week warm-up ramp, the completed pre-flight checklist, and the audit script output showing zero FAILs.
Common failure modes
- Sending cold from the primary domain. The classic catastrophe - one complaint spike poisons all corporate mail. Always isolate on dedicated domains.
- Two SPF records, or blowing the 10-lookup limit. Both produce a permerror and silently fail SPF - common after adding a second ESP. Flatten or consolidate; the script catches both.
- DMARC stuck at p=none forever. It only monitors, never enforces. Move to quarantine/reject once mail passes cleanly.
- Skipping warmup. Blasting a brand-new domain at full volume is the fastest way to land in spam permanently.
- Treating the daily cap as a target. ~40/mailbox is a ceiling for cold mail; scale with more mailboxes, not more per mailbox.
- Unverified lists / catch-all domains. One high-bounce batch torches well-warmed infrastructure. Verify first; keep bounces under 2-3%.
- Open-tracking on a shared tracking domain, or image-heavy HTML. Both scream bulk campaign. Custom tracking domain or none.
- No monitoring until replies dry up. By then the domain is burned. Watch complaint and bounce rates continuously; enroll in Postmaster Tools from day one.
Quality bar
The setup ships only when: every pre-flight checkbox is ticked; the audit script reports zero FAILs on every sending domain; each mailbox has completed its 2-4 week ramp; the seed test lands in the primary inbox on Gmail and Outlook; and alerts exist for bounce >2%, complaints >0.1%, and reply-rate collapse. If any sending domain is also the corporate domain, do not launch - that configuration belongs to email-deliverability rules and must never carry cold volume.
04 同仓库更多 Skills
有氧运动计划
zone-2-cardio-plan
当有人要求制定稳态有氧训练计划、进行有氧基础训练或达到第二区间、安排轻松或耐力型的每周有氧训练、按心率区间训练,或提高静息心率、最大摄氧量或耐力时,可以使用此方案。该方案构建了一个包含训练计划结构、训练方式选择和进度安排的心率区间有氧训练模块。本方案仅提供一般健身指导,不构成医疗建议。请勿用于设计力量或举重训练计划(请使用力量训练计划)、高强度间歇训练 (HIIT) 或循环训练(请使用体能训练和 HIIT 计划),或灵活性/柔韧性训练(请使用灵活性训练计划)。
youtube-script-writer
Writes retention-engineered long-form YouTube scripts on a hook/body/payoff architecture - a first-30-seconds hook that survives the retention cliff, a beat-sheet body with mini-hooks and bridges, timed pattern interrupts, and a payoff that over-delivers - with B-roll and editing cues marked inline. Use when someone says "script my next video", "my retention graph dies at the start", "tighten this draft", or "outline a video on X". Do NOT use for the 3-second opener of TikTok, Reels, or Shorts - use video-hook-writer instead; do NOT use for spoken voiceover synced to on-screen tutorial actions - use narration-script instead; do NOT use for a sales demo built on a buyer's discovery notes - use demo-script instead; do NOT use to design the thumbnail and title - use thumbnail-concept instead.
win-back-campaign
Designs segmented win-back email sequences for dormant users - dormancy tiers, a three-beat sequence with a single proportional incentive, and a suppression rule - and measures success by re-activation, not opens. Use when someone says "our users went quiet", "design a win-back campaign", "how do I re-engage lapsed customers", "should we email people who haven't logged in for months", or lifecycle messaging has stopped landing with a lapsed segment. Do NOT use for recovering an unfinished checkout - use abandoned-cart-sequence instead. Do NOT use for diagnosing and fixing why active customers cancel - use churn-reduction instead. Do NOT use for building always-on onboarding or nurture flows - use email-drip-builder instead.
wikipedia-style
Writes neutral, verifiable, encyclopedic prose following Wikipedia's core content policies and manual of style - NPOV, inline citations, notability-backed claims, and a strict ban on promotional language. Use when someone asks "write this like a Wikipedia article", "draft a Wikipedia page for this company or person", "make this neutral and encyclopedic", or "why does my article draft keep getting rejected". Do NOT use for persuasive or opinion writing - use op-ed-writer instead; for thesis-driven academic papers, use academic-essay; for verifying claims in existing text, use fact-checker.
whitepapers
Structures and writes authoritative B2B whitepapers - a standalone executive summary, a data-sized problem statement, methodology, evidence-driven findings, recommendations, and a soft call to action - flagging every claim that needs a real citation. Use when a marketer asks "write a whitepaper on this topic", "turn our research into a lead-gen asset", "make this credible enough for executives", or a draft reads like a product pitch instead of analysis. Do NOT use for customer-story-driven proof - use case-study-builder instead; for policy audiences, use policy-brief.
white-space-analysis
Finds unmet market gaps by ranking jobs-to-be-done on importance versus satisfaction, overlaying them against current solutions and workarounds, and validating that each gap is real, winnable, and worth winning. Use when someone asks "where are the gaps in this market", "what's underserved here", "where should we play next", or is hunting for product, feature, or market-entry opportunities. Do NOT use for profiling competitors and building battle cards - use competitive-intelligence instead; for putting a dollar figure on an identified opportunity - use market-sizing instead; for extracting jobs-to-be-done from raw customer interviews - use jtbd-extractor instead.