CLI Quickstart
Install the Molted CLI, authenticate your agent, and send your first policy-checked email from the terminal in under two minutes.
1. Sign up
Signup is open. If it's ever paused, auth signup returns a 403 SIGNUPS_CLOSED error with a waitlist link to hand a person. Can't run npx? The same signup works over plain HTTP: see Programmatic signup. Already have an account? Use auth login.
npx @molted/cli auth signup \
--name "My Agent" \
--email agent@yourapp.com \
--password your-passwordThis creates your account (a workspace with a default mailbox on the shared agent.molted.email domain), mints an API key labelled cli, and saves the credentials to ~/.molted/profiles/default.json (or ~/.molted/profiles/<name>.json with --profile <name>). All future commands authenticate automatically; MOLTED_API_KEY in the environment overrides the stored key.
An account created this way is agent-owned: no person owns the workspace until a human claims it with a link from molted auth login-link (see Links for your human).
After signup, the CLI runs a brief onboarding sequence -- it fetches your default mailbox, optionally sets an autonomy level (--autonomy-level 1|2|3), and prints safety hints and next steps. To skip this and get a minimal JSON response (useful in scripted agent provisioning):
npx @molted/cli auth signup \
--name "My Agent" \
--email agent@yourapp.com \
--password your-password \
--skip-onboarding--skip-onboarding and --autonomy-level are also available on auth login, which signs in to an existing account and mints a fresh key each time (older keys stay valid).
Already have an API key from the portal (Settings > Access)? Initialize the CLI directly:
npx @molted/cli auth init --key mm_live_xxxJoining an existing workspace: molted adopt
If someone already runs the workspace, don't sign up: adopt into it. The owner gives your agent an invite token (molted adopt create on their side, or the portal), or approves a device-flow request:
npx @molted/cli adopt claim adopt_... # with an invite token
npx @molted/cli adopt request --label "sales-agent" # device flow: prints a URL and code for the owner, then waitsEither way the CLI stores a key scoped to what the owner granted, then asks GET /v1/agent/setup for your next step. See Adoption & Device Flow for both flows.
2. Activate billing
New workspaces start on the trial plan with sends blocked (every send returns trial_not_activated). Check with molted billing status (sendBlocked: true), then activate a plan to unlock sending.
Paid plan, --plan solo ($19/mo: 10,000 sends a month, 1,000 a day, 10 mailboxes, 3 custom domains) or --plan team ($79/mo: 50,000 a month, 5,000 a day, 50 mailboxes, 10 custom domains). A first paid plan starts with a 3-day trial on restricted limits (100 sends, 3 mailboxes, 1 custom domain); add --skip-trial to start paying right away:
npx @molted/cli billing setup --plan soloOpen the returned checkout URL to complete payment -- open it yourself or send it to a human. Sends unblock once the checkout completes. molted auth login-link hands the workspace to your human: while no person owns it, a take-over email to the account's address when that is their own inbox (they set their own password there) or a claim link (they sign in or create an account and become its owner); otherwise the portal URL. It never signs anyone in.
There is also a no-card Free plan (molted billing activate-free: 3,000 sends a month, 100 a day after a two-week ramp, 3 mailboxes, no custom domains), but it only activates for a verified human account owner. An account created with auth signup is agent-owned, so this 403s with free_plan_requires_human. For Free, run molted auth login-link --next "/app/settings#plan" and follow what it returns; once your human has taken over or claimed the workspace, activate-free works with your existing key. Or use billing setup above. See Billing & Plans for the full rule and error codes.
3. Add a sending domain (optional)
Optional for a first send: the default mailbox on the shared agent.molted.email domain sends without any DNS setup. Custom domains need a paid plan.
npx @molted/cli domains add yourapp.comYou'll get back a domain ID and DNS records (DKIM, SPF, DMARC) to add at your DNS provider. domains add also takes --open-tracking, --click-tracking and --tracking-subdomain <name>. After adding the records, verify using the domain ID from the add response:
npx @molted/cli domains verify <domain-id>A send from a mailbox on a custom domain you haven't verified is blocked with sender_domain_not_verified. Complete verification before sending from it.
4. Send your first email
npx @molted/cli send \
--to recipient@example.com \
--subject "Hello" \
--body "First email from the CLI!"The --to flag takes a full email address (e.g. recipient@example.com).
The --subject and --body flags are shorthand for sending with the _default template, and the CLI generates a dedupe key for you. For HTML email, use --html instead of --body. For templates with custom payloads, use --template and --payload:
npx @molted/cli send \
--to recipient@example.com \
--template my-template \
--dedupe-key "hello-1" \
--payload '{"subject": "Hello", "firstName": "Ada"}'A successful response:
{
"requestId": "9b2c4e1a-...",
"status": "queued"
}status can also be scheduled, pending_approval (held for a human, see Approval Queues) or blocked, with a reason (see Errors). A dedupe key is used once per workspace: reusing it returns the first request as blocked with reason: "duplicate". Other useful flags: --mailbox <id> (send from a specific mailbox), --reason <text> (stored with the send), --reply-to <address>, --attachments <json>, and --dry-run (the same policy check as molted send simulate, nothing delivered).
Add --no-humanize to deliver a send exactly as written, or --humanize --humanize-style casual to ask for the humanizer in a specific style. molted send batch and molted threads reply take the same flags. Run molted send --help for the full flag list.
To see how the humanizer would rewrite an email before sending it, preview it (nothing is sent; a preview uses one humanizer credit):
npx @molted/cli humanizer preview --template welcome --payload '{"firstName": "Ada"}' --diffAn applied rewrite comes with a preview id. To send exactly that rewrite, pass it with the same content: --humanize-preview <previewId> on molted send or molted threads reply. See Preview a rewrite and Send exactly what you previewed.
5. Check delivery
npx @molted/cli trace 9b2c4e1a-...This shows the request with its delivery status, the policy decision, every delivery event (with the provider) and the audit trail (GET /v1/ops/trace/:requestId). molted timeline <requestId> shows the same send as a timeline.
Using the CLI without npx
If you prefer, install globally:
npm install -g @molted/cliThen use molted directly instead of npx @molted/cli:
molted send --to recipient@example.com --template _default ...Updating
molted updateWhen a newer version is available, molted update installs it and shows a changelog summary of what changed between your old version and the new one:
{
"status": "updated",
"from": "1.13.0",
"to": "1.16.0",
"changelog": [
{
"version": "1.16.0",
"date": "2026-04-02",
"changes": [
"Add mailbox delete, clone, and stats commands",
"Add --payload flag to send simulate for policy testing"
]
}
],
"changelogUrl": "https://molted.email/changelog",
"hint": "Updated successfully. Run `molted --version` to verify."
}In interactive terminals, a human-readable summary is also printed to stderr so you can see what changed at a glance.
Troubleshooting: molted doctor
To find out whether this key can send yet, or why mail isn't sending, run:
molted doctorThe "can I send?" checks run server-side in GET /v1/agent/setup (the same checklist the MCP setup_status tool returns): credentials, plan (trial, Free or paid, and whether sends are blocked), holds (an account-level pause, exhausted send quota, held inbound mail), the mailbox a send would use, its sending domain, what policy would do with a first send (sent, held for human approval, or blocked, from the mailbox's real autonomy level), and whether anything has been sent yet. Doctor leads with READY TO SEND or NOT READY TO SEND YET, the next step and whose move it is (yours, or your human's with the call that makes the link to hand them), then shows each step as a check. It also runs the after-send checks: items waiting for human approval, recent policy blocks, the dead-letter queue, and active alerts.
{
"setup": {
"readyToSend": false,
"next": {
"step": "plan",
"owner": "human",
"do": "Ask your human to activate a plan (Free or paid) so sends unblock. Call POST /v1/agent/login-token (`molted auth login-link`) and follow its answer: `kind: \"takeover\"` means Molted emailed the owner address a link to set a password and take this account over (no url: tell your human to check that inbox), `kind: \"claim\"` gives a link to claim the workspace (valid 7 days), `kind: \"portal\"` a link to sign in with their own login.",
"request": { "method": "POST", "path": "/v1/agent/login-token", "body": {} }
},
"steps": [
{ "id": "plan", "status": "blocked", "owner": "human", "why": "Sends are blocked: the workspace is still on the trial plan and no plan has been activated.", "fix": "..." }
]
},
"blockers": [
{
"check": "plan",
"detail": "Sends are blocked: the workspace is still on the trial plan and no plan has been activated. (your human's move)",
"fix": "The owner is a verified human, so they can activate the Free plan themselves in the portal (Settings, Plan) or buy Solo or Team. ... Request: POST /v1/agent/login-token"
}
],
"checks": [
{ "name": "credentials", "status": "ok", "detail": "done: This key works for workspace tenant-slug-a1b2c3d4 with access to every mailbox." },
{ "name": "plan", "status": "blocker", "detail": "Sends are blocked: ...", "fix": "..." },
{ "name": "sending_domain", "status": "ok", "detail": "optional: you@agent.molted.email is on the shared domain agent.molted.email, which works for a first send. ..." },
{ "name": "first_send", "status": "ok", "detail": "todo: Nothing has been sent yet.", "fix": "..." },
{ "name": "held_approvals", "status": "ok", "detail": "Nothing waiting for human review." }
]
}A blocked or open setup step is a blocker. first_send is the goal, not a prerequisite: nothing sent yet never makes doctor fail.
By default it prints a human-readable report to stdout instead; add --json for the shape above. molted doctor --mailbox <id> checks one mailbox the key may send from (and narrows held approvals to it) -- <id> must be a UUID (e.g. 3f1e0c2a-8b7d-4a3e-9c1f-2b6a7d8e9f01); a non-UUID value is rejected before any request is made, and a mailbox outside the key's scope is a blocker.
Exit code is 1 if any blocker was found (not ready to send); otherwise 2 if any check reports "status": "error" (the call itself failed) or "status": "unknown" (the call worked but couldn't be fully classified); otherwise 0. 0 means ready to send and every check fully verified, not just "nothing blocked" -- so a script or agent loop can gate on it:
molted doctor --json > /dev/null || echo "blocked, or not fully verified -- see molted doctor for why"An after-send check that needs a scope the current key doesn't have (operator, for held approvals and alerts), or that hits a mailbox outside a scoped key's access, reports "status": "skipped", not "error" -- it isn't broken, the key just can't see that particular signal, and a skipped check doesn't affect the exit code on its own (the human-readable report says "N check(s) skipped" instead of claiming everything was fully verified). A 401 collapses into a single "auth" blocker regardless of how many of the underlying checks hit it. A malformed --mailbox (not a UUID) exits 2, not 1 -- 1 is reserved for an actual diagnosed blocker, and in that case no check even ran.
molted doctor calls no write endpoint. The setup check is read-only; checking alerts may notify configured alert destinations (deduplicated). Every fix it prints that would loosen a protection (unpausing a mailbox, resuming a paused account, choosing a plan) is phrased as something a human does, never something the command runs for you.
Next steps
- Full CLI reference: /cli -- every command group, subcommand and flag (for example
molted adoptfor joining an existing workspace) - Sending Email -- templates, payloads, and deduplication
- Domains -- DNS setup and verification
- Policy Simulation -- simulate sends before going live (
molted send simulate) - Adoption & Device Flow -- add more agents to the workspace (
molted adopt) - Agent command reference -- the single-file reference written for agents (
molted --skillprints it too)