Audits one lifecycle programme across every system that messages the same people: lifecycle platform, CRM marketing email, billing and product notifications, SMS and push. It rates seven areas PASS, WARN or FAIL per rule and ends in a coverage map, script evidence and a prioritised gap list with change requests. A configuration finding is a lead until data or a seed test confirms it.
The rule: a send count, an attributed revenue figure or a green DNS checker proves nothing about one customer. Every FAIL cites a configuration quote, a script line, a header excerpt or a dated dashboard reading.
When to use
Use this skill when:
- Someone asks for an audit, health check or review of a lifecycle, CRM marketing, email or retention programme.
- An ESP renewal, migration, new lifecycle lead or agency handover needs a baseline.
- People get too many messages, or messages after unsubscribing.
- Gmail, Yahoo and Microsoft bulk sender rules need checking, or someone asks whether lifecycle revenue is incremental.
Use something else when (if a planned skill is not installed, use its fallback):
- One journey needs designing or rewriting: journey-spec-and-qa. Fallback: the contract fields in journey-coverage.md.
- Deep Braze QA, or one named person's missing Braze message: braze-implementation-qa.
- Customer.io or Klaviyo implementation QA: customer-io-implementation-qa, klaviyo-implementation-qa. Fallback: platform-customerio.md, platform-klaviyo.md.
- A HubSpot portal, CRM configuration, pipeline or lifecycle stage audit: crm-audit. Here HubSpot is only a sender: marketing email, sending workflows, subscription types.
- Designing a preference centre, cap service or suppression architecture: preferences-caps-and-suppression. Fallback: record the gap and a change request.
- Designing a holdout or lift test: incrementality-test-design. Fallback: measurement-and-holdouts.md.
- Outbound sequencers and their gates: gtm-workflow-design. Cold domains and warm-up: email-sender-infrastructure. A sequencer reaching these people is still a sender here (FC04, FC08).
- Website consent banners and consent mode: consent-debugging or consent-mode-v2-setup.
- Choosing or migrating a platform: lifecycle-platform-selection, braze-to-customer-io-migration-kit. Fallback: this audit of the current platform is the baseline.
- CRM duplicates and merges: crm-data-hygiene-audit, identity-merge-qa. Fallback: TD07 and PC09 cover only the consent side.
- Sync design: reverse-etl-and-audiences. Fallback: TD05 and PC01.
- Connecting MCP servers: marketing-mcp-setup. Fallback, and Iterable: each platform file's access section.
- Copy review is out of scope, except promotions in transactional messages (PC07) and copy that assumes an unchecked state (JC05).
Inputs
Read first, if they exist in martech-context/ (example-acme-stack.md shows the fields):
stack.md: senders, domains, DKIM selectors, syncs, owners.tracking-plan/: trigger and exit events.data-dictionary.md: preference fields, writers, update schedules.metrics.md: outcomes and windows.decisions.md: cap policy, quiet hours, consent model per region, sunset and holdout decisions.
Then gather:
- Senders in scope with owners, the business model (saas, ecommerce, app, leadgen), regions, channels and depth.
- The live journey and recurring campaign inventory with entries and sends for the window, and source-of-truth counts per trigger with their query.
- Dashboard reads of what APIs do not return: entry, exit and send settings, caps, quiet hours, holdouts.
- A 30-day send log for all senders and a 90-day preference log keyed by internal IDs or salted hashes, plus the holdout list with membership dates.
- Each sender's suppression list (hard bounces, complaints, unsubscribes, with timestamps) keyed like the send log (FC08).
- 30 days of bounce and deferral counts per stream by provider and SMTP reply, no recipient addresses (DL16).
- 30 days of DMARC aggregate data per primary domain (source IPs and DKIM d= per source) from the rua mailbox or reporting tool.
- The raw source of one recent message per stream, saved from a seed inbox the team controls.
- Postmaster compliance and spam rate per primary domain; Yahoo and Microsoft feedback counts if enrolled.
- Every impact claim with its method; who approves and runs seed tests, and with which controlled mailboxes.
Read-only access per platform:
- Braze: remote MCP as a user without edit rights, Operator Connect excluded (platform-braze.md).
- Customer.io: MCP with the default
readscope only, or thecioCLI signed in with a read-only token (platform-customerio.md). - Klaviyo: custom connector with
read-only=true&disable-tools-with-user-generated-content=true(platform-klaviyo.md). - HubSpot: remote MCP allowing only the listed read tools (platform-hubspot.md).
- Iterable: MCP (beta) with writes, sends and PII off (platform-iterable.md).
- Other senders: read window type, retry, exit and default subscription states from live docs.
Keep exports and outputs in one folder of the user's project, never the skill folder; commands below shorten each script's path to scripts/.
Never ask for, print or store keys, passwords, tokens or contact details. message_pressure.py refuses keys that look like contact details; hash SMS keys before export. Do not open customer profiles; person-level checks use seeds. If the account read is not the one named, stop.
Procedure
0. Scope and depth
- Scan: reads, exports and scripts, each finding labelled confirmed by data, configuration-only or unconfirmed.
- Full: Scan plus seed tests, only after the plan is approved. Focused: chosen areas. Default: Scan.
- Windows: 30 days for activity and sends; 90 days for imports and preference changes; holdouts since the last assignment change.
- Any area left out is NOT CHECKED with the reason; a silent skip fails acceptance.
1. Baseline and sender inventory
- Per sender: platform, workspace, region, channels, From and Return-Path domains, DKIM d=, owner, access mode and scopes, read time.
- A sender found in DMARC reports, Postmaster or seed headers but missing from
stack.mdis DL01 WARN and joins the inventory.
2. Journey coverage
Start from the header row of journey-inventory-template.csv, with expected counts from the source of truth minus entry-level exclusions, then:
python3 scripts/inventory_audit.py inventory.csv --model saas --global-holdout yes \
--policy-caps-all-optional yes --as-of 2026-10-14
- Set
--modelfrom the business model,--global-holdoutand--policy-caps-all-optionalfrom decisions.md (both default to no), and--as-ofto the last day of the window. - JC02 ratio, rated when expected is 100 or more: 0.90 to 1.10 PASS; 0.75 to 0.90 or 1.10 to 1.25 WARN; below 0.75 FAIL (leakage); above 1.25 FAIL (duplicates).
- JC03: live with 0 entries in 30 days is FAIL when the source shows occurrences, else WARN.
- JC09: live with entries, but 0 sends or a message step that sends nothing or sits in a non-sending status, is FAIL.
- JC04: 2 or more messages or any delay with no exit on the goal is FAIL.
- JC05: a delay over 1 hour with no send-time recheck is WARN; FAIL when the copy states the unfinished state or offers a discount.
- JC01: a required type with no live journey or external owner is WARN, P1 when revenue- or deliverability-critical, else P2.
- JC07: two live journeys of one type is WARN; FAIL when both reach the same people within 24 hours.
- Models, default stages, columns and method: journey-coverage.md.
3. Triggers, event data and segments
Trace each journey with a FAIL or WARN: producer, path, occurrence ID, 90-day imports, freshness of filter and recheck fields (triggers-data-and-segments.md).
- TD01: client-side triggers on commercial or account facts are WARN; FAIL when failed attempts enter or JC02 is above 1.10.
- TD02: same profile and occurrence within 60 seconds is FAIL.
- TD03: trigger events carry the occurrence ID the platform dedupes on; Braze does not dedupe, so its producer must.
- TD04: imports need a documented gate; an entry spike on an import date for profiles older than 30 days is FAIL.
- TD05: a field updating less often than the shortest delay reading it is FAIL.
- TD06: nulls above 5% WARN, 20% FAIL.
- TD07: duplicates above 1% WARN; mixed-consent clusters FAIL.
- SG02: engagement on opens alone is WARN.
- SG03: over 20% of marketing sends to people with no click, reply, session or purchase in 180 days is WARN.
4. Frequency caps and suppression
python3 scripts/message_pressure.py sends.csv --cap-24h 2 --cap-7d 5 --quiet 21:00-08:00 \
--prefs prefs.csv --holdout holdout.csv
- Pass
--cap-24h,--cap-7dand--quietonly from the written policy. With no policy, omit them: FC05 runs as a WARN screen and FC07 is NOT CHECKED. Add--window calendarwhen the policy or a Braze cap counts calendar days (FC03). - FC02: every optional journey is covered by its sender's cap (Customer.io limits cover nothing until assigned).
- FC05: anyone over a documented cap is FAIL; with no policy, over 2 per rolling 24 hours or 5 per 7 days is WARN.
- FC04: over the policy while each sender is within its own cap is FAIL.
- FC06: Customer.io auto-retry on a state-sensitive message is FAIL (retries continue after exit).
- FC07: quiet-window sends WARN, SMS FAIL.
- FC08: a send after another sender suppressed the address is FAIL.
- Semantics, send log and timestamp formats: caps-and-suppression.md.
5. Preferences and consent
Map every control to its promised scope, each sender's field, the authoritative writer and a seed test (consent-and-preferences.md).
- PC02, P0: an optional send in the scope more than 48 hours after a restrictive change, or at any time after a stale write reversed it, is FAIL. Sends between 1 and 48 hours are PC03 evidence; sends within 1 hour are in flight.
- PC01: a sync reversing an "off" with no customer action is FAIL.
- PC03, unsubscribe to suppression in every sender: 1 hour or less PASS; up to 48 hours WARN; longer FAIL.
- PC04: a promised scope a sender does not enforce is FAIL.
- PC05: default states are not consent; FAIL where decisions.md requires explicit opt-in.
- PC07: promotions classed transactional are FAIL.
- PC08: any text after STOP beyond one confirmation is FAIL, at any delay.
6. Deliverability and authentication
Dig SPF, DMARC and the DKIM selector from a received message, then:
python3 scripts/check_message_headers.py newsletter.txt lifecycle.txt --class marketing
- DL02: Gmail bulk is about 5,000 a day to personal Gmail on the primary domain, permanently. Microsoft's rule starts at 5,000 a day to its consumer mailboxes from one From domain. Below them, pass
--volume lowor--microsoft-volume low. - DL05: bulk streams need dmarc=pass on received mail.
- DL06: SPF and DKIM must both pass for Microsoft bulk.
- DL04: keys under 1,024 bits FAIL.
- DL07: one HTTPS List-Unsubscribe,
List-Unsubscribe-Post: List-Unsubscribe=One-Click, both in DKIM h=, and a cookie-free POST that does not redirect; any header gap on a bulk stream is FAIL. A body with no unsubscribe or preferences link is FAIL; a preferences link only is WARN. - DL16: sender-requirement rejections or deferrals from Gmail, Microsoft or Yahoo in 30 days of bounces on a bulk stream are FAIL; so is a Spamhaus listing of a brand domain or dedicated IP.
- DL09: Postmaster Needs work is FAIL.
- DL10: spam rate under 0.10% PASS, under 0.30% WARN, else FAIL; no Postmaster data is NOT CHECKED.
- DL11: hard bounces above 1% WARN, 2% FAIL.
- Providers, codes, DNS and denominators: deliverability.md.
7. Measurement and holdouts
List each impact claim with its method; run holdout_math.py plan and result (measurement-and-holdouts.md).
- MH01: attributed revenue shown as impact without a control is WARN; so is credit summed across senders without deduplicating by order or conversion ID.
- MH03: any optional send to a holdout member while assigned is FAIL.
- MH04: withheld required messages are FAIL.
- MH05: SRM p below 0.0005 is FAIL.
- MH08: openers against non-openers is FAIL.
- MH09: an MDE above the decision threshold is WARN.
8. Confirm with seed tests (Full depth)
- Plan from seed-test-plan-template.yaml, get explicit approval, and use a test workspace or controlled identities only.
- A person the user names runs each test and the agent records the evidence; a test the agent runs goes through agent-write-safety with that approval.
- Every FAIL and P0 or P1 WARN is confirmed by a seed test or data, or carries the reason it could not be (seed-tests.md).
9. Gap list, change requests and report
- When a P0 condition is confirmed (PC02, PC08, PC09, DL05 to DL07 or DL16 on a bulk stream, spam rate 0.30% or more, wrong-recipient content), tell the user at once: the evidence and a drafted pause of that journey or sync field for the owner to approve. Then continue.
- P0 contain now: those conditions. P1 (about 30 days): other FAILs, spam WARN, missing revenue-critical journeys. P2 (about 90 days): WARNs needing decisions. P3: hygiene.
- Score within a priority: reach per month x severity (FAIL 3, WARN 1) x confidence (1.0, 0.6, 0.3) / effort (S 1, M 2, L 4). Owners may reorder with a written reason.
- Start from the header row of gap-list-template.csv. Write a change request per P0 and P1 item from change-request-template.md.
- Write the report from audit-report-template.md. Rules and two worked requests: gap-list-and-change-requests.md.
- Before hand-over, run
python3 scripts/gap_list_check.py gaps.csvand fix every ERROR. - Propose martech-context updates; write nothing until the user approves.
Common failure modes
- Two senders, no shared budget. Detect: the FC04 cross-sender line.
- A nightly sync resubscribes people. Detect: PC01 stale write, PC02; seed S11.
- Unsubscribe lands on a duplicate profile. Detect: TD07 mixed-state clusters, PC09; seed S06.
- A live journey enters people and sends nothing: a step left in draft or manual. Detect: JC09.
- Stale copy after a block (Customer.io retries after exit; Braze advances capped users). Detect: sends after the goal (FC06).
- Trigger only the web app emits, or a renamed event empties a journey. Detect: JC02 below 0.75 with TD01; JC03.
- Payment reminders continue after payment. Detect: JC04; JC07 against billing dunning.
- Newsletter signed with the provider's domain. Detect: DL05 with spf and dkim passing for another domain; Gmail 4.7.32 deferrals (DL16).
- Blended complaint rate hides Gmail. Detect: Postmaster 0.10% or more with a low ESP rate (DL10).
- Holdout contaminated by a second sender, or split broken after assignment. Detect: MH03, MH05.
Worked example
worked-example.md: the audit run on fictional Acme Reports, with expected output, regression fixtures and the finished gap list.
Acceptance checks
- Baseline: every sender from
stack.md, headers, DMARC data and Postmaster is listed with access mode, scopes, window and read time. Pass: no sender found later is missing. - Coverage: inventory rows match each platform's live list; the model is named and N/A types justified; JC01 to JC09 reported with the stage view. Pass: every journey with 100 or more expected entries has a ratio or a reason.
- Every rule: TD, SG, FC, PC, DL and MH rules each appear as PASS, WARN, FAIL or NOT CHECKED with a reason. Pass: none silently skipped.
- Evidence: every FAIL cites its evidence; in Full depth every FAIL and P0 or P1 WARN is confirmed by a seed test or data, or says why not. Pass: no confirmed finding without evidence.
- Scripts: each ran on the exports, or the report says "script not run" and how the rule was applied. Pass: appendix outputs match the findings and no input held contact details.
- Gap list: every FAIL and WARN appears once with priority, owner, effort, confidence, score and evidence; P0 and P1 items have change requests with test, rollback and what rollback cannot undo. Pass: gap_list_check.py reports 0 ERROR and a colleague could act without asking.
- Safety: the tool log shows reads only, with MCP modes recorded; no credential or contact detail in chat or files. Pass: zero agent write calls outside approved seed tests run through agent-write-safety; every seed test names its approval and who ran it.
- Context: proposed martech-context updates are listed. Pass: none written without approval.
Boundaries
- Read-only. Never create, edit, launch, pause, trigger, send, subscribe, unsubscribe, suppress, merge, delete, import or sync anything, even when the connected identity could. Fixes are change requests; P0 containment is a drafted pause of the journey or sync field, never a deletion.
- Seed tests that create profiles, send messages or reply STOP need explicit approval and a test workspace or controlled identities. A person the user names runs them, or the agent through agent-write-safety.
- The consent model per region belongs to the privacy owner; the audit compares configuration with the written decision.
- DNS and DMARC changes are drafts for the domain owner. Never propose quarantine or reject before every legitimate sender is aligned.
- Hand-offs, read-only: the vendor MCP servers and Customer.io CLI in the modes above, Customer.io's workspace-health command, Gmail Postmaster Tools, Yahoo Sender Hub, Microsoft SNDS and JMRP, and a read-only warehouse connection.
- Writes go to agent-write-safety; fixes to the owners in the gap list.
Live docs
Platform settings, limits and menu paths change. Before stating any parameter name, limit, quota or UI path:
- Fetch the current page from the vendor docs MCP server or llms.txt listed in
sources.yaml, or open the canonical URL. - If the live page disagrees with this skill, follow the live page and tell the user what changed.
- Cite the URL you used.
Most important here: each platform's caps, subscription, events and MCP pages; the Gmail, Yahoo and Microsoft sender pages and Gmail's SMTP error reference; RFC 8058 and RFC 9989. Sender rules, Postmaster and MCP pages: rechecked 2026-11-06. Others: 2026-11-01 or 2026-11-02 unless marked "verify live"; verify every Iterable setting live.