# Cold email infrastructure, end to end

Most outbound stacks buy the domain in one place, host DNS in another, send from a third, and discover the authentication was wrong when the reply rate is already zero. The pieces are not hard. The seams between them are.

Eight capabilities cover the whole path here: find a domain, register it, write the DNS, verify it for sending, check the authentication independently, create the inbox, send, and read what comes back.

## The pipeline

```
domain.search        →  what is available and what it costs
domain.register      →  buy it, with you as registrant
email.domain         →  verify it for sending, get the DNS records
domain.dns           →  write those records
email.inbox          →  the sending address
email.send           →  send, or reply in a thread
email.messages       →  replies, bounces and opens
```

## Step 1: a domain that is not your main one

Never send cold outbound from your primary domain. A deliverability problem on a sending domain costs you a campaign; the same problem on the domain your invoices and password resets go through costs you the company's email.

```bash
curl -X POST https://api.routergrowth.com/v1/run \
  -H "Authorization: Bearer $ROUTERGROWTH_API_KEY" \
  -d '{"capability": "domain.search", "input": {"name": "acme", "tlds": ["com", "io", "co"]}}'
```

Availability and price per TLD. Pick something close to your brand and readable when a recipient checks it: `getacme.com`, `acme-hq.com`, `tryacme.com`. Avoid a TLD with a bad sending reputation, and avoid anything that looks like a throwaway, because the recipient's spam filter has an opinion about that too.

```bash
curl -X POST https://api.routergrowth.com/v1/run \
  -H "Authorization: Bearer $ROUTERGROWTH_API_KEY" \
  -d '{
    "capability": "domain.register",
    "input": {
      "domain": "getacme.com", "years": 1, "privacy": true, "auto_renew": true,
      "registrant": {"first_name": "Alex", "last_name": "Rivera",
                     "email": "alex@acme.com", "country": "FR"}
    }
  }'
```

You are the registrant contact. The response carries the order, the expiry and the nameservers.

## Step 2: authentication, which is the whole game

```bash
curl -X POST https://api.routergrowth.com/v1/run \
  -H "Authorization: Bearer $ROUTERGROWTH_API_KEY" \
  -d '{"capability": "email.domain", "input": {"domain": "getacme.com"}}'
```

This returns a `domain_id`, a `status`, the exact DNS `records` to create and a `setup_url`. Write the records with `domain.dns`:

```bash
curl -X POST https://api.routergrowth.com/v1/run \
  -H "Authorization: Bearer $ROUTERGROWTH_API_KEY" \
  -d '{"capability": "domain.dns",
       "input": {"domain": "getacme.com", "records": [ ... from email.domain ... ]}}'
```

Three records decide whether your mail is read or filed:

- **SPF** lists which servers may send for the domain. One record, and only one. Two SPF records is a permanent fail, and it is the single most common misconfiguration.
- **DKIM** signs each message so the receiver can verify it was not altered. Missing DKIM is a soft fail at most providers and a hard one at some.
- **DMARC** tells receivers what to do when SPF or DKIM fails, and where to send reports. Start at `p=none` to collect reports without rejecting your own mail, then tighten to `quarantine` once the reports are clean.

Google and Yahoo both require authentication for bulk senders, and enforcement has only tightened. Unauthenticated cold mail does not land in spam, it frequently does not arrive at all.

## Step 3: inbox, send, read

```bash
curl -X POST https://api.routergrowth.com/v1/run \
  -H "Authorization: Bearer $ROUTERGROWTH_API_KEY" \
  -d '{"capability": "email.inbox",
       "input": {"name": "alex", "domain": "getacme.com", "display_name": "Alex Rivera"}}'
```

Then send, with an unsubscribe URL, because you are the sender of record:

```bash
curl -X POST https://api.routergrowth.com/v1/run \
  -H "Authorization: Bearer $ROUTERGROWTH_API_KEY" \
  -H "Idempotency-Key: campaign-q4-lead-4821" \
  -d '{
    "capability": "email.send",
    "input": {
      "inbox_id": "inb_...", "to": "alex.rivera@acme.com",
      "subject": "3 lifecycle roles in 45 days",
      "text": "Saw you posted three lifecycle marketing roles...",
      "unsubscribe_url": "https://getacme.com/u/abc123"
    }
  }'
```

`email.messages` polls the inbox for replies, bounces and opens as labels. Bounces matter more than replies in week one: a hard bounce rate above 2% is a sign the list was not verified properly, and it is the fastest way to lose a new domain.

## Warm-up, honestly

A new domain has no sending history, which means no reputation, which means receivers are suspicious by default. The pattern that works is boring:

| Week | Volume per day | What you are watching |
| --- | --- | --- |
| 1 | 10 to 20 | Bounces. Anything above 2% means stop and re-verify the list. |
| 2 | 30 to 50 | Replies. Any engagement at all is reputation. |
| 3 to 4 | 75 to 150 | Spam complaints. Above 0.1% and you back off. |
| 5+ | Scale gradually | Reply rate by segment, not aggregate volume. |

Verify every address with `contact.verify` before it enters this. The [waterfall enrichment guide](/use-cases/waterfall-email-enrichment) covers that half of the pipeline.

## Where it breaks

- **DNS propagation is not instant.** Run `email.domain` again until it reports the domain verified before you send, not immediately after writing records.
- **Two SPF records is a hard failure.** If the domain had any prior mail setup, look before you add.
- **Volume is not the constraint people think it is.** Reputation is earned per recipient domain. A thousand sends a day to one large provider is a different risk from a thousand spread across many.
- **You are the sender of record.** Consent, unsubscribe handling and the applicable rules (CAN-SPAM, GDPR, and whatever applies where your recipient is) are yours. The API sends what you tell it to.

## Run it as an agent

```bash
routergrowth run -c domain.search -i '{"name":"acme","tlds":["com","io"]}'
routergrowth run -c email.domain -i '{"domain":"getacme.com"}'
```

An agent can run the whole setup and re-check verification on a schedule, which is the part humans stop doing after month two.

## FAQ

### Why not just send from my company domain?

Because reputation is attached to the domain, and cold outbound is the highest-risk mail you send. A spike in complaints or bounces on a sending domain costs you that campaign. The same spike on your primary domain can affect password resets, invoices and every internal thread. Separate domains contain the blast radius, and they let you retire one without consequence.

### How long does warm-up actually take?

Four to six weeks to reach meaningful volume, and the number that decides it is engagement, not days. A domain sending 20 verified emails a day that get replies warms faster than one sending 200 to a cold, unverified list. If bounces exceed 2% or complaints exceed 0.1%, the correct move is to reduce volume, not to wait longer at the same volume.

### Do I need DMARC at p=reject?

Not to start, and starting there is a common way to block your own mail. Begin at `p=none`, which collects reports and rejects nothing, and read the reports for a few weeks to see everything legitimately sending as your domain. Move to `quarantine`, then `reject`, once that list is empty of surprises.

### Can I use my own domain instead of registering a new one?

Yes. `email.domain` takes any domain you control, returns the records to add and reports verification status. You write the records wherever your DNS lives. Registering through `domain.register` is a convenience so that the whole path is one API, not a requirement.
