---
name: send-gate
description: The single approval choke point between drafted outreach and an actual send. Use when a batch of drafted messages (cold email, LinkedIn DMs, connection requests, follow-ups) is ready to go out. Runs suppression, dedupe, and daily caps, then holds everything for explicit human approval. Never sends anything itself. Not for drafting copy: use cold-email. Not for follow-up timing: use sales-cadence. Not for suppression list upkeep: use do-not-contact.
license: MIT
metadata:
  author: TechTide AI (Alex Cinovoj)
  provenance: original
  category: Sales & RevOps
---

# Send Gate

One choke point between "message drafted" and "message sent". No draft becomes a send without passing suppression, dedupe, and caps, then getting explicit human approval, item by item or in bulk. A send that bypasses this gate is a gate violation, full stop.

## The Three Guarantees

1. Every candidate passes a live do-not-contact check at batch time, never a cached verdict.
2. Nobody already contacted (per the outreach log) gets touched again by accident.
3. Daily per-channel caps hold, counting sends already logged today.

## Canonical Files

Keep all gate state under `pipeline/` in the working repo:

- `pipeline/drafts.jsonl`: drafted records awaiting the gate (`status=drafted`)
- `pipeline/send-gate/config.json`: caps and freshness rules, created with defaults on first run
- `pipeline/send-gate/batches/YYYY-MM-DD.jsonl`: one batch file per day
- `pipeline/outreach-log.jsonl`: append-only record of committed sends

Default config:

```json
{
  "daily_caps": {"linkedin": 10, "linkedin_dm": 10, "email": 20},
  "max_suppression_age_hours": 24,
  "batch_requires_explicit_approval": true
}
```

## Daily Workflow

1. **Refresh suppression.** Rebuild the do-not-contact list so yesterday's bounces, replies, and opt-outs take effect today. If the list is missing or older than `max_suppression_age_hours`, hard stop: no batch until it is rebuilt.
2. **Build the batch.** For each drafted record: drop it if it matches the suppression list (report as `suppressed`), drop it if its email, profile URL, or name already appears in `outreach-log.jsonl` (report as `already_touched`), and hold it if the channel's cap is already met counting today's logged sends (report as `held_over_cap`). Write survivors to today's batch file with `gate_status=needs_approval`. Print a summary: per-channel counts, suppressed, already_touched, held_over_cap.
3. **Review with the approver.** Present each record's name, company, channel, and full draft text. The approver (`<APPROVER>`) approves, edits, or rejects each one. No approval, no send. Fix voice and copy problems at review time, not after.
4. **Record outcomes same day.** After each approved send happens (manually or via a send surface), append one entry to `outreach-log.jsonl` with the key, channel, timestamp, and the exact text sent if edited. Mark skipped or rejected records in the batch file only. Dedupe and cap math depend on the log being current.

## Operating Rules

1. This gate is the only path to a send. Drafting tools, cadence loops, and agents hand drafts to the gate; they never send directly.
2. Suppressed hits are dropped and reported. Never queue them for later.
3. Caps are safety floors, not targets. `held_over_cap` records stay drafted and surface in tomorrow's batch.
4. Every batch record must end the day resolved: `sent`, `skipped`, or `rejected`. An unresolved record means the run is not closed.
5. Replies route onward: positive replies to the deal pipeline, negatives and opt-outs into the suppression list before the next batch.

## Verification

Before closing the run, do this:

- Count approved sends in today's batch file, then count new lines appended to `outreach-log.jsonl` today. Expect them equal. If not, find the unlogged send and record it now.
- Check the suppression list timestamp. Expect it within `max_suppression_age_hours` of batch build time. If stale, the batch was invalid: rebuild suppression and re-verify no suppressed contact was sent.

## Good vs Bad

**Bad:** Suppression list is 3 days old, but the batch is small and "these leads look clean", so the agent builds and presents it anyway. One of them opted out yesterday. That is exactly the incident the gate exists to prevent.

**Good:** Agent detects the stale list, halts the batch, rebuilds suppression from the latest bounces and replies, then builds. Two records drop. The approver sees a clean batch.

## Footguns

- **Cached suppression verdicts.** Checking a list loaded hours ago misses today's opt-outs. Fix: check freshness at batch build time, hard stop if stale.
- **Dedupe by one key only.** The same person appears as an email in one record and a LinkedIn URL in another. Fix: dedupe on email, profile URL, and normalized full name.
- **Unlogged manual sends.** Someone sends an approved draft but never records it, so tomorrow's cap math and dedupe are wrong. Fix: the run is not closed until log count matches approved count.
- **Editing the draft after approval.** Approval covers specific text. Fix: material edits go back through review; log the exact text sent.

## Red Flags: Stop Immediately

- "It's just one message, skip the batch."
- "The suppression list is probably fine from yesterday."
- "They're clearly interested, approval is a formality."
- "We're under cap anyway, send the extras too."
- "I'll log these sends tomorrow."

Every one of these is a bypass attempt. There is no batch size, urgency level, or confidence score that exempts a send from suppression, dedupe, caps, and explicit approval.

## Completion Checklist

- [ ] Suppression list fresh at build time, hard-stopped if not
- [ ] Batch built with suppression, dedupe, and cap results reported
- [ ] Every record explicitly approved, edited, or rejected by the approver
- [ ] Every approved send appended to the outreach log same day
- [ ] Every batch record resolved (sent, skipped, or rejected)

Any box unchecked: not done. Fix or say so.
