This project currently uses a lightweight local smoke workflow instead of a full test framework.
The smoke flow exercises:
- admin token minting
- signed bearer auth
- agent creation
- mailbox binding
- mailbox listing assertion
- draft creation
- draft fetch assertion
- draft send enqueue
- persisted outbound job assertion
- SES webhook ingestion
The signup + site smoke flow exercises:
- merged runtime worker serving the public site at
/ - merged runtime worker serving the admin dashboard at
/admin - reserved self-serve aliases rejected at
POST /public/signup - successful self-serve signup against the local
.testdomain when routing bootstrap is intentionally skipped - mailbox-scoped access token usability after signup
- welcome-email delivery without creating billing reservations or debit ledger entries
The MCP smoke flow exercises:
- MCP transport
OPTIONSand placeholderGEThandling - MCP JSON-RPC notifications, batches, parse errors, and empty-batch rejection
- MCP same-origin
Originenforcement for browser-style callers - MCP
initialize - MCP
tools/list /v2/meta/compatibility/v2/meta/compatibility/schema- MCP provisioning tools
- MCP mailbox-scoped
list_messages - MCP draft creation and send
- MCP high-level
send_email - MCP high-level
reply_to_message - MCP idempotent send replay
- MCP composite reply workflow success path against seeded inbound mail
- MCP machine-readable error codes
The billing + DID smoke flow exercises:
- tenant-scoped bearer token minting
- default billing account initialization
- default tenant send policy initialization
- hosted
did:webbinding creation - public DID document resolution
- x402
402 Payment Requiredquote flow for topups - pending topup receipt creation
- manual payment settlement into the credit ledger
- x402 upgrade intent quote flow
paid_reviewupgrade settlement- admin approval transition to
paid_active
- local worker already running with
npm run dev:local - local D1 already migrated and seeded
jqinstalled
chmod +x scripts/local_smoke.sh
ADMIN_API_SECRET_FOR_SMOKE=replace-with-admin-api-secret \
WEBHOOK_SHARED_SECRET_FOR_SMOKE=replace-with-shared-secret \
./scripts/local_smoke.shOptional overrides:
BASE_URLTENANT_IDMAILBOX_ID
chmod +x scripts/mcp_smoke.sh
ADMIN_API_SECRET_FOR_SMOKE=replace-with-admin-api-secret \
./scripts/mcp_smoke.shOr:
npm run smoke:mcp:local
npm run smoke:mcp:devThe MCP smoke script expects the demo seed to include:
- seeded inbound message
msg_demo_inbound - seeded thread
thr_demo_inbound
When targeting deployed dev, the script defaults to the admin secret in
.dev.vars. Override ADMIN_API_SECRET_FOR_SMOKE explicitly if the deployed
environment uses a different admin secret.
This production-safe smoke covers only public or intentionally disabled surfaces:
- home page availability
- runtime and compatibility metadata
- public signup method, content-type, JSON-shape, and field validation
- public token-reissue method, CORS, JSON-shape, required-field, and alias validation
- generic accepted token-reissue response for a clearly nonexistent mailbox
- admin MCP disabled posture without requiring an admin secret
Run:
npm run smoke:production:public
BASE_URL=https://api.mailagents.net bash ./scripts/production_public_blackbox_smoke.shOptional overrides:
BASE_URL
This smoke is intended for production-like environments where admin and debug routes are disabled. The broader post-deploy verifier now runs both:
bash ./scripts/production_readonly_smoke.shbash ./scripts/production_public_blackbox_smoke.sh
chmod +x scripts/billing_did_smoke.sh
ADMIN_API_SECRET_FOR_SMOKE=replace-with-admin-api-secret \
./scripts/billing_did_smoke.shOr:
npm run smoke:billing:local
npm run smoke:billing:local:auto
npm run smoke:billing:facilitator:local:autoOptional overrides:
BASE_URLTENANT_IDAUTH_SCOPE_FOR_SMOKEX402_PAYMENT_SIGNATURE_FOR_SMOKEPAYMENT_CONFIRM_MODE_FOR_SMOKE
PAYMENT_CONFIRM_MODE_FOR_SMOKE=facilitator exercises facilitator-backed
confirmation and expects the worker environment to set
X402_FACILITATOR_URL=mock://local or a real facilitator base URL.
For the local mock facilitator path, the fastest command is:
npm run smoke:billing:facilitator:local:autoThis flow is the closest current check to a real x402 payment:
- it requests a live
402topup quote from deployeddev - it submits a real signed x402 v2
exact/eip3009proof toPOST /v1/billing/topup - with facilitator-backed
dev, the payment receipt may already besettledin the initial topup response - it retries
POST /v1/billing/payment/confirmbyreceiptIdonly when a facilitator-backed retry is still needed
Prerequisites:
- deployed
devhasX402_PAY_TOconfigured .secrets/dev-base-sepolia-wallet.jsonexists locally and holds a funded Base Sepolia wallet- the wallet has enough Base Sepolia ETH for gas and USDC for the requested amount
- local
ethersis available, or setETHERS_PATH
Run:
npm run smoke:billing:dev:real-chain
npm run smoke:billing:dev:real-chain:facilitator
npm run smoke:billing:dev:real-chain:upgradeIf the wallet is low on Base Sepolia funds, you can refill it from the local CDP faucet helper:
npm run faucet:cdp -- --token usdc
npm run faucet:cdp -- --token eth --token usdc --with-balances
npm run faucet:cdp -- --balances-onlyThis helper expects:
.secrets/cdp_api_key.jsonwith a valid CDP API key.secrets/dev-base-sepolia-wallet.jsonwith the destination Base Sepolia address
Optional overrides:
CDP_API_KEY_JSON_PATHWALLET_JSON_PATHFAUCET_ADDRESSFAUCET_NETWORKFAUCET_TOKENS
Optional overrides:
BASE_URLBASE_RPC_URLWALLET_JSON_PATHETHERS_PATHCREDITS_TO_BUYOPERATOR_EMAIL_FOR_SMOKEPAYMENT_CONFIRM_MODE_FOR_SMOKE
This script proves:
- the live quote fields are correct
- the current
payToaddress is really reachable onchain - chain-backed payment proof capture works against deployed
dev - settlement lands in receipts, ledger, and billing account state
Current note:
PAYMENT_CONFIRM_MODE_FOR_SMOKE=manualis no longer supported becausePOST /v1/billing/payment/confirmnow only retries facilitator-backed settlement byreceiptId- use the default facilitator path, or set
PAYMENT_CONFIRM_MODE_FOR_SMOKE=facilitatorexplicitly - the facilitator variant proves the deployed runtime can automatically execute
verify -> settle, but it still does not prove a third-party facilitator is live unless the environment points at a real one
This flow exercises the paid upgrade path with the same live dev deployment:
- it requests a live
402upgrade quote from deployeddev - it builds a real x402 v2
exact/eip3009payment payload against Base Sepolia USDC - it submits that payload to
POST /v1/billing/upgrade-intent - it expects the facilitator-backed path to settle immediately
- it verifies the tenant lands in the environment's configured post-upgrade state
Prerequisites:
- deployed
devhasX402_PAY_TOconfigured - deployed
devpointsX402_FACILITATOR_URLat a working facilitator .secrets/dev-base-sepolia-wallet.jsonexists locally and holds a funded Base Sepolia wallet- the wallet has enough Base Sepolia ETH for gas and USDC for the quoted amount
Run:
npm run smoke:billing:dev:real-chain:upgradeOptional overrides:
BASE_URLWALLET_JSON_PATHETHERS_PATHOPERATOR_EMAIL_FOR_SMOKE
Current note:
- with facilitator-backed
dev, the initialupgrade-intentresponse may already return asettledreceipt before the explicit confirm retry runs
This script proves:
- low-value upgrade quotes display correctly, including sub-cent prices like
0.001 - the facilitator-backed
upgrade-intentflow accepts a real signed x402 payload - successful settlement applies the configured upgrade transition for that environment
This regression covers the exact flow that previously broke:
- self-serve signup starts with an internal-only agent recipient allowlist
- topup settles successfully
- external send is accepted immediately after topup, even while send policy still
reports
internal_only - upgrade settles successfully
- external send is still accepted after upgrade without requiring a manual
agent:updatepolicy patch
Run:
npm run smoke:upgrade-unlock:local:autoThis script proves credits now unlock external recipient delivery for the
default self-serve agent policy, and that the later upgrade path does not
reintroduce the old allowedRecipientDomains=["mailagents.net"] restriction.
The D1 migrate scripts are now safe to rerun against an existing local or remote
database. They record applied files in schema_migrations and bootstrap that
state from the already-present schema when upgrading an older environment.
This smoke focuses on the default internal-only send posture. It expects the
local demo seed (t_demo, mbx_demo, agt_demo) to be present.
chmod +x scripts/outbound_quota_regression_smoke.sh
ADMIN_API_SECRET_FOR_SMOKE=replace-with-admin-api-secret \
SES_MOCK_SEND=true \
SES_MOCK_SEND_DELAY_MS=1500 \
./scripts/outbound_quota_regression_smoke.shOr:
npm run smoke:quota:local
npm run smoke:quota:local:autoNotes:
- The script creates a real local recipient mailbox and verifies that two
mailbox-to-mailbox sends succeed while the tenant remains
internal_only. - It confirms the recipient inbox sees
provider = "internal". - It tightens the sender agent recipient allowlist to prove true internal mailbox routing bypasses external-domain policy checks.
- It verifies external-recipient sends are still blocked under the same default
policy, and that
to=[internal], cc=[external]is rejected synchronously instead of creating an accepted-but-doomed outbound job.
This smoke focuses on the reserve -> capture -> release path for external sends.
It expects the local demo seed (t_demo, mbx_demo, agt_demo) to be present.
chmod +x scripts/outbound_credit_smoke.sh
ADMIN_API_SECRET_FOR_SMOKE=replace-with-admin-api-secret \
SES_MOCK_SEND=true \
SES_MOCK_SEND_DELAY_MS=1500 \
./scripts/outbound_credit_smoke.shOr:
npm run smoke:credits:local
npm run smoke:credits:local:autoNotes:
smoke:credits:local:autostarts the worker withSES_MOCK_SEND=trueand a small mock delay so the script can assert the intermediateavailableCredits/reservedCreditsreservation state before capture. The flag is currently reused as the generic outbound mock toggle even whenOUTBOUND_PROVIDER=resend.- The script seeds additional local credits into the demo tenant, keeps the
tenant send policy on
internal_only, verifies one successful external send still captures a reserved credit, then verifies one suppressed-recipient send releases its reservation without adding a debit ledger entry. - Run
npm run d1:seed:localfirst if the demo tenant or mailbox is missing.
This smoke focuses on the merged-worker setup and self-serve signup regressions.
chmod +x scripts/signup_site_smoke.sh
ADMIN_API_SECRET_FOR_SMOKE=replace-with-admin-api-secret \
SES_MOCK_SEND=true \
SES_MOCK_SEND_DELAY_MS=1500 \
./scripts/signup_site_smoke.shOr:
npm run smoke:signup:local
npm run smoke:signup:local:autoNotes:
smoke:signup:local:autostarts the local worker with mocked outbound delivery so the welcome email can complete deterministically.- The script verifies the same local worker serves
/,/admin, and/public/signup, which is the intended post-merge deployment model. - It rejects the reserved
helloalias, creates a new self-serve mailbox, then verifies the welcome send does not create any billing reservation or ledger debit.
This smoke focuses on the admin manual-resolution path for uncertain sends.
chmod +x scripts/outbound_uncertain_resolution_smoke.sh
ADMIN_API_SECRET_FOR_SMOKE=replace-with-admin-api-secret \
./scripts/outbound_uncertain_resolution_smoke.shOr:
npm run smoke:uncertain:local
npm run smoke:uncertain:local:autoNotes:
- The script covers
not_sent,sent, and delivery-evidence conflict handling. - It now also verifies that a missing draft payload causes manual resolution to fail closed instead of settling or releasing billing with empty recipients.
- For local setup, it seeds credits directly into the demo tenant so the smoke stays focused on uncertain-send resolution rather than the separate x402 topup flow.
The current shared dev environment is:
https://mailagents-dev.izhenghaocn.workers.dev
Use the same scripts with BASE_URL overrides:
BASE_URL='https://mailagents-dev.izhenghaocn.workers.dev' \
ADMIN_API_SECRET_FOR_SMOKE=replace-with-admin-api-secret \
WEBHOOK_SHARED_SECRET_FOR_SMOKE=replace-with-shared-secret \
bash ./scripts/local_smoke.shBASE_URL='https://mailagents-dev.izhenghaocn.workers.dev' \
ADMIN_API_SECRET_FOR_SMOKE=replace-with-admin-api-secret \
bash ./scripts/mcp_smoke.shBefore running remote smoke:
- deploy with
npm run deploy:dev - apply
npm run d1:migrate:remote:dev - apply
npm run d1:seed:remote:devif the seeded inbound MCP flow is needed - confirm
ADMIN_API_SECRET,API_SIGNING_SECRET, andWEBHOOK_SHARED_SECRETare configured as Worker secrets fordev - billing + DID smoke also requires migrations
0006through0008, because it exercises billing accounts, DID bindings, and tenant send policies
The main purpose of this page is to explain current smoke coverage and how to run it.
For dated dev and production verification records from the March 2026 rollout
window, see docs/archive/2026-03-runtime-verification.md.
Current interpretation guidance:
- shared
devhas been verified live during the current rollout era, but the detailed dated evidence now lives in the archive - production has also been verified for the controlled support-mailbox path, but dated record IDs and incident notes now live in the archive
- treat any archived verification record as evidence from a specific point in time, not as proof that the same state still holds today
Fixtures live in:
You can post them manually:
curl -X POST http://127.0.0.1:8787/v1/webhooks/ses \
-H 'content-type: application/json' \
-H 'x-webhook-shared-secret: replace-with-shared-secret' \
--data-binary @fixtures/ses/delivery.json- The smoke script does not guarantee the configured outbound provider actually delivered an outbound message.
- It verifies that the local API and queue-facing lifecycle can be exercised end to end.
- It asserts key response fields with
jqso obvious regressions fail fast. - Debug endpoints are admin-secret protected and intended only for local/dev verification.
- The billing + DID smoke intentionally uses manual settlement via
x-admin-secret; it is a regression harness for the current skeleton flow, not proof that a facilitator integration is live. - For the first real Base Sepolia + USDC payment run, follow docs/x402-real-payment-checklist.md after facilitator credentials and
X402_PAY_TOare configured. - For production confidence, add real integration tests around D1 state assertions and provider callback handling.
- If SES is still sandbox-limited, external outbound smoke coverage must be scoped to verified recipient addresses.
- In deployed
dev, the negative MCP mailbox-binding check can legitimately return eitherresource_mailbox_not_foundoraccess_mailbox_denied, depending on token mailbox scope. - historical rollout-era evidence now lives under docs/archive/
When older inbound messages were normalized before RFC 2047 subject decoding landed,
their stored messages.subject values may still look like =?UTF-8?...?=.
Use the backfill tool to preview and optionally repair those rows from raw EML in R2:
npm run backfill:subjects:dev -- --dry-run
npm run backfill:subjects:production -- --dry-runTo scope the scan:
node ./scripts/backfill_message_subjects.mjs --env production --mailbox mbx_4ee06ae7768c4b3f95f22cb2e7b57ce4 --limit 20
node ./scripts/backfill_message_subjects.mjs --env production --message-id msg_f025694cda9043bab2521bfabd338bffTo apply updates after reviewing the dry run:
node ./scripts/backfill_message_subjects.mjs --env production --apply --limit 20The tool only updates inbound messages whose stored subject currently begins with
an encoded-word prefix and whose raw .eml object still exists in R2.