← Building a solo company with AI agents

Part 7 of 40 · 7 min read

Campaigns and outbound: keep the send button separate from the draft

Experimental · Work in progress · Internal R&D

Prepare outreach, check permission and sender readiness, retain approval and measure useful demand without hiding the send decision.

Arunank Sharan · Kuzushi Labs ·

In this article

An assistant can produce a hundred outreach messages before I have decided whether the offer is clear. That speed is only useful if the campaign has an audience, a budget, a reason to contact that audience and a way to connect responses back to commercial outcomes.

This is a focused sub-loop of Market to Lead. The prototype models campaign stages, content checks, launch, analysis and sending-domain health. Its Google Ads and email-provider adapters record simulated actions. No live advertising spend or email delivery is implied by a campaign reaching LIVE in a local fixture.

Begin with a campaign brief

For the fictional studio, the offer is a bounded document-search pilot. A useful brief says which organisations face the problem, what the pilot delivers, which claims can be supported and what response we want. “Book a scoping conversation” is a different objective from “buy a subscription.”

campaign: document-search-pilot
objective: qualified_scoping_conversation
budget_currency: SGD
budget_limit: 600
approved_claims:
  - source-linked answers over a customer's document collection
excluded_claims:
  - guaranteed percentage reduction in support headcount
send_mode: draft_only
owner: founder

This is a proposed campaign configuration, with example amounts. The current campaign object and stage transitions supply the starting structure; the brief needs to become versioned data associated with the approved creative.

A draft does not inherit permission to launch

The local workflow moves a campaign through draft, checks and scheduling before launch. The advertising adapter then projects campaign identity, name, budget and state into an in-memory account. That separation is useful: the loop owns approval and the connector owns provider translation.

For a live send I want an action-specific preview: recipient count, audience selection version, message hash, sending identity, maximum cost and stop condition. An approval for 40 recipients should not remain valid after the audience expands to 4,000.

DecisionAgent can prepareFounder or policy must establish
OfferDraft positioning from approved product factsWhat is actually promised
AudienceCandidate segment and exclusionsPermitted targeting and contact basis
CreativeVariants and evidence-backed claimsApproved version
LaunchProvider request and cost estimateSpend and sending authority
OptimisationMeasured comparisonWhether to change budget or audience

Reputation is part of the action's preconditions

The prototype carries SPF, DKIM, DMARC, bounce and complaint signals, a reputation score, and a daily volume limit. The action policy can deny a send when the recorded sending-domain condition is poor. Its healthy fixture includes a 0.5% bounce rate and 0.02% complaint rate; those are test inputs, not observed campaign results or recommended provider limits.

A stronger implementation should fetch these facts from trusted integrations. Letting the model submit dkim_valid=True is not a reputation check. The same principle applies to unsubscribe_link_present: validate the rendered message or provider configuration rather than trust a supplied Boolean.

Measure an outcome beyond sending

A provider accepting the message is an intermediate result. I want separate records for accepted, delivered, bounced, complained, replied and qualified. When an account buys, the opportunity can point back to the campaign. For ambiguous attribution, preserve the source evidence instead of declaring every recent interaction the cause of the sale.

A small campaign review can show:

Approved recipients:       40
Accepted by provider:      39
Delivered:                 37
Replies:                    5
Qualified conversations:    2
Won customers:              not yet observed

These are illustrative counts. They demonstrate why cost per delivered message and cost per won customer answer different questions. A campaign with no observed wins has an unknown acquisition cost for wins; reporting zero would be misleading.

The first live experiment should be easy to stop

I would start with draft-only generation against approved contacts, then a small explicitly authorised send batch. One complaint or a suppression mismatch should stop the relevant sending operation until reviewed. That operational threshold can be deliberately conservative without being represented as a universal rule.

The public reading kit uses simulated providers and sample addresses. It demonstrates request preparation and receipts without sending messages. The next implementation work is authenticated transport, suppression synchronisation, version-bound approvals and reconciliation of provider events. The business objective is a founder who can understand why a campaign ran and what came from it, rather than a larger pile of AI-generated copy.

Freeze the audience and the message before approval

A campaign draft is not one string. It includes audience definition, exclusions, message version, sending account, contact purpose and a spending ceiling. The approval should bind to those values. If a later enrichment job expands the audience, the old approval should not silently cover the additional people.

The reference contact rule is deliberately narrow: the example requires explicit opt-in for the approved purpose, no suppression and the approved content version. It is a local operating policy, not a statement of marketing law.

from company_lab.business import can_contact

decision = can_contact(
    permission="OPT_IN",
    scope="requested-pilot-followup",
    current_scope="requested-pilot-followup",
    suppressed=False,
    approved_version=4,
    version=4,
)
assert decision
assert not can_contact(
    permission="OPT_IN", scope="requested-pilot-followup",
    current_scope="newsletter", suppressed=False,
    approved_version=4, version=4,
)

The same customer can be eligible for a reply about a requested pilot while being excluded from a newsletter. Keeping purpose in the record makes that distinction enforceable. A generic can_email=True field cannot express it.

Recheck suppression at the point of send

Suppose the founder approves a batch at 09:00 and a contact opts out at 09:05. The worker sends at 09:10. Snapshot approval is useful for the content, but current suppression must be checked again. The pending recipient operation should then close as suppressed, with no external effect.

A practical batch item can look like this:

{
  "campaign": "pilot-followup-04",
  "message_version": 4,
  "audience_snapshot": "audience-04",
  "contact": "contact-017",
  "purpose": "requested-pilot-followup",
  "sender_connection": "studio-sales",
  "operation": "campaign-04-contact-017-v4",
  "state": "QUEUED"
}

The stable operation identity is specific to recipient and content version. A retry uses the same identity. A genuinely new message needs a new approval and operation. Provider delivery events can then update accepted, delivered, bounced or complained states without turning them into new campaigns.

Campaign approval fixes the audience and content while the send worker checks fresh suppression and records delivery outcomes.

Budget the campaign in money and attention

The local campaign code demonstrates checks and projections. A live advertising connector needs a separate launch transaction: reserve the approved budget, submit the provider campaign, retain its ID and reconcile its effective state. A provider accepting a configuration does not prove that the campaign is delivering or spending at the intended rate.

I would begin with an explicit example ceiling of S$200 for a small experiment, plus a daily spend alert and a manual stop action. The precise amount is a scenario choice. The important implementation detail is that the current committed spend, outstanding reservations and new request are checked together. Two launch workers must not each see the full remaining budget.

For outreach, founder attention can become the bottleneck. If a campaign generates 30 complex replies while delivery is already full, lead count alone is a misleading success measure. Feed available delivery capacity and accepted-customer outcomes into planning before increasing volume.

Evaluate the message and the effect separately

A message can be well written but sent to the wrong recipient, outside its approved purpose or after an opt-out. Conversely, a properly suppressed message may look like a failed send in a naive dashboard. The evaluation should inspect the intended recipient, current permission, approved message version and provider receipt.

For an A/B experiment, persist assignment before sending and retain that assignment through retries. Use a business outcome such as qualified replies or accepted opportunities, with a stated observation window. Do not treat email opens as conclusive evidence of attention; provider behaviour and privacy features can distort that signal. The first useful experiment asks which message produces a relevant conversation at an acceptable review cost.

The exercise for this chapter is a three-recipient batch: one eligible, one newly suppressed and one whose provider acknowledgement is lost. The expected result is one confirmed effect, one suppression and one reconciliation task. The founder can inspect all three without reading the agent's entire conversation.