Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AgentMail × Sapiom — standalone end-to-end demo

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.


Why two surfaces

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.


Pre-demo checklist (do the first one TODAY, not right before recording)

  • A domain is VERIFIED in AgentMail. Confirm it today: … --check-domain <domain> should print status: 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_ADDRESS is 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_ADDRESS is an address you control (for the outbound test mail, and for the optional inbound reply with --await-inbound).
  • Env is set — copy .env.example.env and fill it in.

Setup

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

Run

# 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.

Modes (pick one)

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.

Flags

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).

Typical demo (safe path)

node --env-file=.env node_modules/.bin/tsx demo.ts --use-existing-domain harrydu.dev --await-inbound

Prints, 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.

Full registration (record once; costs a real domain)

# 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.dev

🎬 Recording script — both surfaces, one domain (time-lapse the wait)

This 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_KEY and put the new key in .env--register is a real charge, don't run it with a key that's been shared anywhere. (2) --register requires --yes (or an interactive y/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 --yes

What 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-inbound

What 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.


Environment variables

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.


Notes / gotchas baked into the script

  • domains.get/verify take a domainId, not the name. The script resolves name → id via domains.list() first; --resume-verify calls domains.verify(id) (nudges the re-check) then domains.get(id) (reads status) on each poll.
  • MessageItem has no body — the script messages.gets the newest message to show its text.
  • Sent vs inbound is inferred from the message labels (sent ⇒ outbound). --await-inbound waits for the first non-sent message.
  • Failures print the failing call + response body and exit non-zero — no raw stack trace mid-demo.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages