A throwaway script that proves the full workflow on screen:
register a domain through Sapiom → create an inbox under it → send mail → read the inbox.
It is not production code: not wired into NestJS, touches nothing under src/providers/, and
does not need to merge. It exists to demo the pattern so it can later be wired into Sapiom's
custom-domain cold-outreach flow.
Sapiom's AgentMail passthrough is still a TODO(PR 3) stub
(src/providers/agentmail/agentmail.controller.ts only exposes /v1/webhooks). So the demo splits:
| Step | Surface |
|---|---|
| Domain check / register | Sapiom namecom gateway over HTTP (namecom.{SERVICES_DOMAIN}/v1/domains*) |
| Domain DNS (optional) | Sapiom namecom DNS (…/v1/domains/:name/records) |
| Inbox / send / list | AgentMail SDK ([email protected]) called directly |
Auth is one header. All Sapiom calls use x-sapiom-api-key: <SAPIOM_API_KEY> — nothing else.
Read-only routes (check / status / DNS) are identity-gated (free); register is priced and billed
server-side via the gateway's x402 "collapsed flow" (it holds the request open ~30s while
Sapiom authorizes the purchase). The script never signs an X-PAYMENT header or touches a wallet.
- A domain is VERIFIED in AgentMail. Confirm it today:
… --check-domain <domain>should printstatus: VERIFIED. This is the long-pole — if it's not verified, you need lead time for DNS/SES to propagate. - SES sandbox? If the AgentMail SES account is still in sandbox, sends only succeed to
SES-verified recipients. Make sure
DEMO_TO_ADDRESSis verified, or confirm the account is out of sandbox (ask Haakam). If a send is rejected, the script prints a sandbox-aware hint. -
DEMO_TO_ADDRESSis an address you control (for the outbound test mail, and for the optional inbound reply with--await-inbound). - Env is set — copy
.env.example→.envand fill it in.
This is a self-contained project — it has its own package.json and bundles the
agentmail SDK, so it runs from its own folder with no monorepo involvement.
cd ~/Desktop/Sapiom_workspace/agentmail-sapiom-demo
npm install # one time — installs agentmail + tsx
cp .env.example .env
# edit .env with your keys# Node loads the .env, tsx runs the TypeScript directly:
node --env-file=.env node_modules/.bin/tsx demo.ts <mode> [flags]That node --env-file=.env … tsx demo.ts prefix is the run command throughout this README
(abbreviated as … demo.ts below). Requires Node ≥ 20.6 for --env-file.
| Command | What it does |
|---|---|
--check-domain <domain> |
Preflight: is the domain VERIFIED in AgentMail? Run today. |
--use-existing-domain <domain> |
Safe default. Inbox → send → read against a pre-verified domain. |
--register <domain> |
Full flow: check + register (real $) → domains.create → pause. |
--resume-verify <domain> |
Poll AgentMail until VERIFIED, then inbox → send → read. |
| Flag | Effect |
|---|---|
--dry-run |
With --register: check + print "would register / bill $X", skip the real POST. |
--yes, -y |
With --register: skip the interactive confirm and proceed with the real purchase. Without it, --register prints the ~$X cost and waits for a y/N confirmation (and refuses on non-interactive stdin). |
--auto-dns |
With --register: add the verification DNS records via Sapiom's namecom DNS. Records are normalized from AgentMail's FQDN name to a name.com relative host (e.g. _dmarc.acme.dev → _dmarc); the mapping is printed so you can eyeball it. |
--await-inbound |
After send: also poll the inbox for an incoming message (non-blocking). |
node --env-file=.env node_modules/.bin/tsx demo.ts --use-existing-domain harrydu.dev --await-inboundPrints, step by step: confirm domain VERIFIED → create inbox → send email → list inbox + show the newest message body → (optional) wait for your reply to land and print it.
# 1) iterate safely first:
… --register newdomain.dev --dry-run
# 2) for real (this BILLS a domain purchase via the collapsed flow):
… --register newdomain.dev
# → prints DNS records + pauses. Add them (or use --auto-dns).
# 3) once DNS is live:
… --resume-verify newdomain.devThis is the take that shows Sapiom dynamic domain creation AND AgentMail inbox/send/read as a single story on one domain. The only edit is time-lapsing the DNS/SES verification wait.
Let D be a fresh, unregistered domain you're willing to buy (e.g. acme-demo-7.dev). Every
command is shortened to … demo.ts; the full prefix is node --env-file=.env node_modules/.bin/tsx.
Before a real run: (1) rotate
SAPIOM_API_KEYand put the new key in.env—--registeris a real charge, don't run it with a key that's been shared anywhere. (2)--registerrequires--yes(or an interactivey/N) to actually spend.
Take 1 — Sapiom dynamic registration (record live):
# Optional warm-up shot (no spend): show the Sapiom availability check + price.
node --env-file=.env node_modules/.bin/tsx demo.ts --register $D --dry-run
# The real thing (REAL ~$X charge): registers through Sapiom (x402 collapsed flow bills the
# purchase), registers the domain in AgentMail, and AUTO-ADDS the verification DNS records back
# through Sapiom's namecom DNS — all on screen. --yes skips the interactive confirm.
node --env-file=.env node_modules/.bin/tsx demo.ts --register $D --auto-dns --yesWhat the audience sees: check → confirm/register (Sapiom owns the domain) → domains.create
(AgentMail returns FQDN records) → auto-dns prints the FQDN → relative host mapping and writes
the records → a PAUSE message. Eyeball the mapping — each host should be relative
(_dmarc, <token>._domainkey, mail, or "" for apex); anything flagged ⚠️ passthrough needs a
second look. (Re-running --register $D after this is safe: it detects the owned domain and skips the
purchase.)
⏳ Cut / time-lapse here. DNS + SES verification takes ~5–8 min. Stop recording (or speed-ramp).
Take 2 — finish on the SAME domain (resume recording):
# Polls AgentMail (verify → get) until VERIFIED, then creates the inbox,
# sends an email, and reads the inbox. Add --await-inbound for two-way.
node --env-file=.env node_modules/.bin/tsx demo.ts --resume-verify $D --await-inboundWhat the audience sees: status: PENDING → VERIFYING → VERIFIED → inbox created on D → email
sent → inbox listed + newest message body printed → (optional) your reply appears.
Talk track: "Sapiom registered and DNS-configured a brand-new domain on demand — no registrar account, paid inline via x402. Once AgentMail verified it, we spun up an inbox on that domain and sent + read mail through it. That's the whole custom-domain cold-outreach primitive, end to end."
Tip: to rehearse Take 2 repeatedly without burning domains, keep one already-verified domain around and run
--use-existing-domain <that>— same inbox/send/read steps, no registration.
| Var | Required | Notes |
|---|---|---|
SAPIOM_API_KEY |
reg only | sk_live_… / sk_test_… — sent as x-sapiom-api-key. Needed only for --register / --resume-verify --auto-dns; AgentMail-only modes don't require it. |
SAPIOM_SERVICES_DOMAIN |
reg only* | e.g. services.sapiom.ai. Base URL = https://namecom.<that>. |
SAPIOM_NAMECOM_BASE_URL |
reg only* | Full base-URL override; wins over SAPIOM_SERVICES_DOMAIN if both set. |
AGENTMAIL_API_KEY |
yes | AgentMail SDK bearer key. |
AGENTMAIL_API_URL |
no | Defaults to https://api.agentmail.to. |
DEMO_TO_ADDRESS |
yes | Outbound recipient; an address you control (SES-verified if sandbox). |
DEMO_INBOX_USERNAME |
no | Inbox local-part (default demo). |
DEMO_INBOX_CLIENT_ID |
no | Stable id so reruns reuse the same inbox (default sapiom-demo-001). |
* Set one of SAPIOM_SERVICES_DOMAIN or SAPIOM_NAMECOM_BASE_URL. There is no default — that
is intentional, so you can't accidentally fire real domain registrations at the wrong environment.
domains.get/verifytake adomainId, not the name. The script resolves name → id viadomains.list()first;--resume-verifycallsdomains.verify(id)(nudges the re-check) thendomains.get(id)(reads status) on each poll.MessageItemhas no body — the scriptmessages.gets the newest message to show its text.- Sent vs inbound is inferred from the message
labels(sent⇒ outbound).--await-inboundwaits for the first non-sentmessage. - Failures print the failing call + response body and exit non-zero — no raw stack trace mid-demo.