# Ephemera — Transactional email from a domain on Resend

> Self-executing Markdown. One binding of the *send-transactional-email* intent — siblings:
> [`email.cloudflare.md`](./email.cloudflare.md), [`email.aws.md`](./email.aws.md). The cloud is the
> source of truth; this file is intent + write-back ledger. **This file carries the portability ledger.**

> **Requires** `zone(example.com)` from [`domain.cloudflare.md`](./domain.cloudflare.md) — Resend
> returns DKIM/SPF records you add to the **Cloudflare zone** (`<CF_ZONE_ID>`).
> **Provides** `email-sender(example.com)`.

---

## 🤖 Director prompt

Observe before acting; verify each step; stop at 🔴/💥 for human go; write realized values back into Live
State. **Best DX, fastest to first send** — a test sender works before you touch DNS; no sandbox-approval
gate. `RESEND_API_KEY` is a secret (env / secret store), never inline.

```
Legend  🟢 create · 🟡 config · 🔴 GATE (human go) · 💥 destructive (human go) · ⏳ wait · ✔ verify
```

## Intent

Send transactional email (a website **contact form**) from `contact@example.com`, authenticated so it
lands in inboxes. Resend's edge is **onboarding friction**: an instant test sender (`onboarding@resend.dev`)
proves the integration *before* DNS, the API is modern, and there's no sandbox→production approval. You
still authenticate the domain (DKIM/SPF in the Cloudflare zone) — deliverability is domain auth, the same
on every provider.

**Shared acceptance contract** (every binding must pass):
1. a test email **sends** (provider returns an id)
2. it **lands in a real inbox**, not spam
3. **DKIM=pass, SPF=pass, DMARC=aligned** — read the recipient's `Authentication-Results` header

## Provisioning Inputs

| # | Question | Options (closed enum) | Default | Sets | Gates |
|---|----------|-----------------------|---------|------|-------|
| 1 | Sender address | free-text | `contact@example.com` | `SENDER` | the `from` address |
| 2 | Test recipient | free-text | `you@example.com` | `TEST_TO` | §1/§5 sends |
| 3 | First step | `instant-test` / `straight-to-domain` | `instant-test` | `START` | whether §1 runs |

```yaml
resolved_inputs:
  sender:  contact@example.com
  test_to: you@example.com
  start:   instant-test
```

## Live State

```yaml
status:        not-created      # published template - run it to realize state
last_action:   authored — Resend binding
last_verified: —
```

| key            | value |
|----------------|-------|
| CF_ZONE_ID     | `<CF_ZONE_ID>` |
| SENDER         | `contact@example.com` |
| RESEND_DOMAIN_ID | `—` (from §2) |

| ✔ check                  | expected                              | observed | result |
|--------------------------|---------------------------------------|----------|--------|
| instant test send        | `200` + email id (pre-DNS)             | —        | — |
| domain created           | records array returned                 | —        | — |
| DKIM/SPF in CF zone       | records UPSERTed                       | —        | — |
| domain verified          | status `verified`                      | —        | — |
| test from your domain     | delivered, lands in inbox              | —        | — |
| auth results             | dkim=pass spf=pass dmarc=pass          | —        | — |

## 0. Variables

```bash
export CF_ZONE_ID="<CF_ZONE_ID>"
export SENDER="contact@example.com" TEST_TO="you@example.com"
# secret: export RESEND_API_KEY=...  (never inline it in commands you echo)
```

## Dependency frontier

```
(instant test, no DNS) ─┐
create domain ─> add DKIM/SPF to CF zone (Requires zone) ─> verify domain ⏳ ─> DMARC ─> 🟡 send from domain ─> ✔ auth-results
```

## 1. Instant test — prove the API before DNS  🟢  *(`START=instant-test`)*

```bash
curl -fsS -X POST 'https://api.resend.com/emails' \
  -H "Authorization: Bearer ${RESEND_API_KEY}" -H 'Content-Type: application/json' \
  --data "{\"from\":\"Cybercussion <onboarding@resend.dev>\",\"to\":[\"${TEST_TO}\"],\"subject\":\"Ephemera Resend test\",\"html\":\"<p>It works — before DNS.</p>\"}"
```
> Proves auth + reachability with zero DNS. (Resend's free tier covers this; check current limits.)

## 2. Create the sending domain  🟢

```bash
RESP="$(curl -fsS -X POST 'https://api.resend.com/domains' \
  -H "Authorization: Bearer ${RESEND_API_KEY}" -H 'Content-Type: application/json' \
  --data '{"name":"example.com"}')"
echo "$RESP" | python3 -c 'import sys,json;d=json.load(sys.stdin);print("DOMAIN_ID:",d["id"]);[print(r["type"],r["name"],"->",r["value"]) for r in d["records"]]'
# capture id → RESEND_DOMAIN_ID; capture the records[] (DKIM CNAME/TXT + SPF TXT) for §3
```
> → Live State: RESEND_DOMAIN_ID, records.

## 3. Add Resend's DKIM/SPF records to the Cloudflare zone  🟡  *(Requires `domain.cloudflare.md`)*

```bash
# for each record from §2: UPSERT into the CF zone. (proxied:false — these are mail-auth records, DNS-only)
echo "$RESP" | python3 -c '
import sys,json,os,urllib.request
d=json.load(sys.stdin); zid=os.environ["CF_ZONE_ID"]; tok=os.environ["CLOUDFLARE_API_TOKEN"]
for r in d["records"]:
    body=json.dumps({"type":r["type"],"name":r["name"],"content":r["value"].strip(),"proxied":False,
                     **({"priority":r.get("priority",10)} if r["type"]=="MX" else {})}).encode()
    req=urllib.request.Request(f"https://api.cloudflare.com/client/v4/zones/{zid}/dns_records",body,
        {"Authorization":f"Bearer {tok}","Content-Type":"application/json"})
    try: urllib.request.urlopen(req); print("added",r["type"],r["name"])
    except Exception as e: print("skip/exists",r["type"],r["name"])'
```

## 4. Verify the domain  🟡⏳

```bash
curl -fsS -X POST "https://api.resend.com/domains/${RESEND_DOMAIN_ID}/verify" \
  -H "Authorization: Bearer ${RESEND_API_KEY}"
# poll until verified (DNS propagation, minutes)
for i in $(seq 1 12); do
  S="$(curl -fsS "https://api.resend.com/domains/${RESEND_DOMAIN_ID}" -H "Authorization: Bearer ${RESEND_API_KEY}" \
        | python3 -c 'import sys,json;print(json.load(sys.stdin)["status"])')"
  echo "status: $S"; [ "$S" = "verified" ] && break; sleep 10
done
```

## 5. DMARC (shared; idempotent) + send from your domain  🟡

```bash
# DMARC once in the CF zone (same record all bindings share)
curl -fsS -X POST "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records" \
  -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" -H "Content-Type: application/json" \
  --data '{"type":"TXT","name":"_dmarc.example.com","content":"v=DMARC1; p=none; rua=mailto:you@example.com"}' || echo "dmarc exists"

# send from the verified domain
curl -fsS -X POST 'https://api.resend.com/emails' \
  -H "Authorization: Bearer ${RESEND_API_KEY}" -H 'Content-Type: application/json' \
  --data "{\"from\":\"Cybercussion <${SENDER}>\",\"to\":[\"${TEST_TO}\"],\"subject\":\"Ephemera Resend (domain)\",\"html\":\"<p>From contact@example.com.</p>\"}"
```

## 6. Acceptance verify  ✔

```bash
echo "Open the email at ${TEST_TO} → 'Show original' → expect:  dkim=pass  spf=pass  dmarc=pass"
dig +short TXT _dmarc.example.com   # DMARC present
```
> → Live State: status: live; fill verify rows.

## Teardown  💥

```bash
curl -fsS -X DELETE "https://api.resend.com/domains/${RESEND_DOMAIN_ID}" -H "Authorization: Bearer ${RESEND_API_KEY}"
# remove the DKIM/SPF records this binding added to the CF zone (list + delete by name); leave shared DMARC
```
> → Live State: status: gone.

---

## Portability ledger — one intent, three bindings

| | AWS SES (`email.aws.md`) | Resend (`email.resend.md`) | Cloudflare (`email.cloudflare.md`) |
|---|---|---|---|
| Time to first send | after domain verify **+ sandbox→production approval** (~24h) | **instant** (test sender, pre-DNS) | minutes (domain already on CF) |
| Domain auth (DKIM/SPF) | required — records into the CF zone | required — records into the CF zone | **auto-written** into the CF zone |
| Approval gate | 🔴 sandbox→production (anti-abuse; one-time) | none | none |
| Where DNS lives | Cloudflare zone (cross-provider) | Cloudflare zone (cross-provider) | **same Cloudflare zone** (no cross-provider) |
| DX / API | AWS CLI, clunkier; deep AWS event integration | modern REST, best DX | wrangler / binding / REST |
| Cost | **cheapest at scale** (~$0.10/1k) | free tier → paid | bundled with Cloudflare |
| Lock-in | low (swap = re-point DNS + change API) | low | low |
| Best when | high volume, AWS-native, cost-driven | fastest ship, great DX | already on Cloudflare |

All three pass the **same acceptance contract** (delivered + dkim/spf/dmarc pass); the difference is
friction, cost, and where you already live. The DKIM/SPF records land in the **same Cloudflare zone**
regardless — switching providers is re-pointing a few records, not a migration.

## Deliberately not included

- **Marketing / bulk sending** — transactional only.
- **Inbound** — receiving is a separate intent.
- **The contact-form app** — the consumer of this capability.
