Phase 2 — Outreach (Email)
What this phase delivers: for each qualified Target from Phase 1, send a short sequence of AI-personalised cold emails, land them in the inbox, and feed the outcome (reply / bounce / no-reply) back into the target's lifecycle — so non-responders flow on to Connect (Phase 3).
Builds directly on Phase 1: targets are already discovered, enriched, and benchmark-scored. Outreach works through them highest-score-first.
1. What we build vs. what we integrate (this phase)
Unlike a typical outbound tool, this repo builds the sender layer itself rather than renting it. We build the prompt-driven AI email generation, the 3-mail sequence orchestration, the VirtualCompany / VirtualEmployee synthetic sender identities, and the lifecycle updates that react to replies and bounces.
We integrate the raw delivery rails only — the mailboxes and their warm-up — through Microsoft Graph / Exchange Online and Trulyinbox. Getting cold email into the inbox (domains, sender identities, warm-up, rotation, reputation) is real work, but in this repo it is orchestrated by the platform over managed mailboxes, not delegated wholesale to a third-party sequencing SaaS.
The integrations fall into two layers.
2. Layer 1 — Mailboxes, warm-up & sending (the sender layer we own)
The engine that holds the sending mailboxes, warms them up, and delivers the sequence. In this repo the pieces are:
- Microsoft Graph / Exchange Online — the actual mailboxes that send and receive mail. Mail send/receive goes through the Graph API against licensed Exchange Online mailboxes.
- Trulyinbox — email warm-up, run continuously in the background to build and hold each mailbox's reputation.
- VirtualCompany / VirtualEmployee — synthetic sender identities the platform manages. A
VirtualCompanyowns 1..3VirtualEmployees, and eachOutreachMessageis sent as one of those employees. The platform randomises sending, plans sick days / vacations for virtual employees, and rotates identities so outbound traffic looks human.
| Option | Pros | Cons |
|---|---|---|
| Microsoft Graph / Exchange Online + Trulyinbox + platform-managed identities (this repo) | Full control and ownership of sender identities, rotation, and reputation; mailboxes are real, licensed Exchange inboxes; warm-up handled by a dedicated service; no per-sequence SaaS lock-in; identities integrate directly with the rest of the data model | We own the orchestration (sequence timing, rotation, sick-day simulation); deliverability still depends on our sending behaviour and copy; more moving parts than a turnkey platform |
| Turnkey sequencing SaaS (alternative, not used) | Warm-up, rotation, and reply/bounce webhooks out of the box; fast to stand up | Another subscription; personas/warm-up live in their tool, not ours; re-introduces a vendor seam and cedes control of the sender layer this product treats as core IP |
How it works + docs
- Microsoft Graph / Exchange Online — We send mail via the Graph
sendMailendpoint from a licensed mailbox and read inbound mail (for reply processing) via Graph message/subscription APIs. Auth is app-registration / OAuth against the tenant. Docs: https://learn.microsoft.com/en-us/graph/api/resources/mail-api-overview - Trulyinbox — A warm-up service that ramps and sustains each mailbox's volume/reputation in the background, independent of the live campaign traffic. Docs: https://trulyinbox.com
- Sender identities —
VirtualCompany/VirtualEmployeerecords in our own data model bind a display persona to a mailbox; scheduled jobs (BullMQ) randomise sending windows and plan sick days / vacations so patterns look human. Mail send runs on theoutreach-mailqueue; inbound is processed viawebhooks/notifications.
Decision (this repo): the sender layer is built and owned. Warm-up is Trulyinbox; delivery is Exchange Online via Graph; identities are VirtualCompany/VirtualEmployee. Everything sits behind a single internal sending interface so a rail can be swapped without touching sequence or bandit logic.
3. Layer 2 — AI email generation
Turning a target's enrichment data (industry, website signals, hiring activity, benchmark) into the emails of the sequence, driven by a prompt the team can edit.
| Option | Pros | Cons |
|---|---|---|
| Anthropic API (Claude) (this repo) | Same ecosystem we're already building in; strong at instruction-following and structured output; good for German-language copy | Usage cost per generation; output still needs guardrails/review |
| OpenAI API (GPT) (alternative) | Widely used; large ecosystem | Another vendor relationship; same review/guardrail needs |
How each one works + docs
- Anthropic API (Claude) — We send a system instruction plus the target's enrichment data and get back the email copy, returning structured output (subject + body as separate fields). Every response is schema-validated before use, target/research content is XML-tag-isolated in the prompt (prompt-injection defense), and generated mail passes a plausibility check (required fields present, no hallucinated prices) before it can be sent. This slots into the prompt editor so the team can tune tone and rules without code. Docs: https://docs.claude.com/en/api/overview
- OpenAI API (GPT) — Same request/response shape conceptually; send a prompt with the target data, get back copy. Docs: https://platform.openai.com/docs
Decision (this repo): Anthropic Claude for generation — it's the ecosystem we're already in and gives full control of the prompt. The generator sits behind the prompt editor either way.
4. The bandit — picking the best variant
Outreach content is organised as OutreachVariant → OutreachStep → StepVersion. Each step-version is hashed and audited. A multi-armed bandit (Thompson Sampling default, epsilon-greedy alternative) selects the best-performing StepVersion at send time and learns from reply outcomes:
- Success = a genuine reply (not spam / out-of-office).
- Failure = no reply within the wait window.
Every selection and outcome is appended to bandit_events (append-only — never updated or deleted), which is what makes the learning auditable. This is the outreach content bandit; it is distinct from Phase 5's budget allocation optimization, which rebalances spend across campaigns/sources.
5. How the outreach flow works (the part we build)
Plain-language flow:
- Phase 1 hands us qualified targets, highest benchmark first.
- For each target, the bandit picks the
StepVersionfor mail 1 and Claude writes it from the editable prompt + the target's data. - The platform sends it from a warmed-up Exchange Online mailbox as a chosen
VirtualEmployee. - If there's no reply after the wait window, mail 2 goes out, then mail 3 — a 3-mail sequence, each step bandit-selected.
- Inbound mail is read back via Graph and reply processing classifies it (genuine reply / spam / out-of-office / bounce / unsubscribe).
- We update the target's state: a reply routes it toward sales; three mails with no reply hands it off to Connect (Phase 3); a bounce or unsubscribe takes it out of the flow.
- Each outcome feeds the bandit (
bandit_events) so later sends favour the winning versions.
The benchmark score carries through, so Connect later works the no-responders in the same priority order.
6. The scope note: owning the sender layer
An earlier exploratory design floated renting a turnkey sequencing SaaS to make the fabricated "virtual employees", domain warm-up, randomised sending, and simulated vacations/sick-days redundant. This repo deliberately keeps that layer in-house — the synthetic sender identities and their human-looking behaviour are treated as core product IP, wired directly into the data model and audit trail rather than living inside a third-party tool. Warm-up (the genuinely commoditised, maintenance-heavy part) is the one piece delegated, to Trulyinbox.
7. Legal & compliance notes for this phase
This is the phase where outreach law bites. Note that the full GDPR / spam / UWG compliance layer is explicitly out of scope for v1 in this repo — the points below are design considerations for a written legal opinion before building, not build items:
- UWG (German unfair-competition law) — unsolicited B2B cold email in Germany generally requires prior consent, which directly affects whether and how the email flow may legally operate.
- GDPR — we're contacting named individuals; lawful basis, unsubscribe handling, and retention all apply. Unsubscribe/opt-out should be wired into the target lifecycle when the compliance work is scoped.
- Sender identity — synthetic personas carry legal and reputational exposure; this is a factor in the legal opinion.
Recommendation: get the German data-protection / UWG opinion covering email and calling before building this phase — it may change the sequence design.
8. Decisions needed for Phase 2
- Mailbox provisioning: how many Exchange Online mailboxes / VirtualEmployees per VirtualCompany (1..3), and the licensing plan for them?
- Warm-up: confirm Trulyinbox as the warm-up service and its ramp schedule.
- AI generator: confirm Anthropic Claude (recommended) vs. OpenAI as the generation model.
- Sequence rules: the wait windows between mails 1→2→3, and what exactly counts as "no reply" before handing off to Connect. ⚠️ Needs client sign-off.
- Bandit policy: Thompson Sampling (default) vs. epsilon-greedy, and the reply-window length that defines success/failure.
- Legal sign-off: the UWG/GDPR opinion on cold email — owner and deadline. ⚠️ Needs client sign-off.
- Procurement: who sets up and pays for the Exchange Online licenses, Trulyinbox, and Anthropic accounts? (Third-party secrets live encrypted in the
settingstable, set via UI; the Anthropic key is the one infrastructure credential.)
Appendix — Documentation links
| Integration | Docs |
|---|---|
| Microsoft Graph (Exchange Online mail) | https://learn.microsoft.com/en-us/graph/api/resources/mail-api-overview |
| Trulyinbox (email warm-up) | https://trulyinbox.com |
| Anthropic API (Claude) | https://docs.claude.com/en/api/overview |
| OpenAI API (alternative) | https://platform.openai.com/docs |
Prices, plan gating, and rate limits are approximate and change — confirm on each provider's site. Part of the project docs — Phase 2 of 5.