carlyemail
v0.6.0
Published
Real email inboxes your agent can send, receive and reply from. SDK and CLI.
Maintainers
Readme
carlyemail
Real email inboxes your agent can send, receive and reply from. One package, two things: a typed client and a command line.
The client
npm install carlyemailimport { CarlyEmail } from "carlyemail";
const carly = new CarlyEmail(); // reads CARLYEMAIL_API_KEY
const inbox = await carly.inboxes.create({ username: "hello" });
await carly.messages.send(inbox.email, {
to: ["[email protected]"],
subject: "Hello",
text: "From an agent.",
});Generated from the OpenAPI spec, so
it cannot describe an endpoint the API does not serve. One fetch call per
method and no dependencies, so it runs in Node, the browser, a worker and on the
edge with no build step. Types ship with it.
Errors carry the message, the fix and a documentation link:
import { CarlyEmailError } from "carlyemail";
try {
await carly.inboxes.create({ username: "hello" });
} catch (error) {
if (error instanceof CarlyEmailError) {
console.log(error.status, error.code, error.fix, error.docs);
}
}There is a Python client too: pip install carlyemail.
The command line
npx carlyemail signupIt asks for your email, offers an inbox name, and writes the key to
~/.carlyemail/config.json. Confirm the emailed code and it can send:
npx carlyemail verify 123456
npx carlyemail send \
--from [email protected] \
--to [email protected] \
--subject "Hello" \
--text "Sent by an agent with its own address."Load the saved key and its first inbox into the current shell before starting LangChain, the Claude Agent SDK, Eve, Mastra, or another framework:
eval "$(npx carlyemail env)"Install it if you use it often:
npm install -g carlyemailCommands
| | |
|---|---|
| signup --human-email <e> --username <u> | Create an account and an inbox |
| verify <code> | Confirm the owner email |
| whoami | Identity and scope of the current key |
| env | Print CARLYEMAIL_API_KEY and CARLYEMAIL_INBOX shell exports |
| inboxes | List inboxes |
| create --username <u> | Create an inbox |
| delete <inbox> --yes | Delete an inbox and its mail |
| send --from <i> --to <a> --subject <s> --text <t> | Send an email |
| messages <inbox> | List messages |
| read <inbox> <id> | Read one message in full |
| search <inbox> <text> | Search messages |
| threads <inbox> | List conversations |
| reply <inbox> <id> --text <t> | Reply to the sender (--all to copy everyone) |
| drafts <inbox> | List drafts |
| draft <inbox> --to <a> --text <t> | Write a draft without sending |
| send-draft <inbox> <id> | Send a draft written earlier |
| webhooks | List webhook endpoints |
| domains | List custom domains, with their DNS records |
| plan | Current plan, with usage against every limit |
| upgrade <plan> | A Stripe checkout link |
| billing | Invoices, card changes, cancellation |
| mcp | The MCP endpoint, for Claude and other clients |
Scripting
--json prints the API response untouched — the same shape the
API reference documents, not a
reshaped subset:
carlyemail inboxes --json | jq -r '.inboxes[].email'
carlyemail plan --json | jq '.organization.storage_bytes'Failures still exit 1 under --json, and the error is still printed, so
set -e behaves.
Configuration
The key is saved to ~/.carlyemail/config.json, written 0600 — it can read
and send your mail, so it is not left group-readable.
Two environment variables override it, and the environment always wins:
| | |
|---|---|
| CARLYEMAIL_API_KEY | Use this key instead of the saved one |
| CARLYEMAIL_API_URL | Point at a different deployment |
That ordering is what makes CI and containers work without touching the saved state, and it means running one command as a different account is a prefix rather than a login.
Notes
An owner email is not a login. Running sign-up again for an existing address
returns account_exists and leaves every key untouched. Recover access with an
emailed code at https://console.carlyemail.com; use create from an
authenticated CLI session to add another inbox.
delete requires --yes. It removes the inbox and every message in it,
and there is no undo.
Exit codes are meaningful. 0 on success, 1 on any failure, so
carlyemail send … || handle-it behaves in a script.
Errors carry their fix. The API answers with a message, the thing that clears it, and a documentation link; all three are printed.
✗ Inbox limit reached (3 on the free plan).
Upgrade the plan, or delete an inbox you no longer need.
https://docs.carlyemail.com/inboxesRequirements
Node 18 or newer. No dependencies — this is one file that uses Node's built-in
fetch, so npx never resolves a tree and never fails for a reason unrelated
to the thing you asked for.
Inbound email in a Worker
The dependency-free webhook helper uses Web Crypto, so the same verified
onEmail callback runs in Cloudflare Workers, Node, and other edge runtimes:
import { createEmailHandler } from "carlyemail/webhooks";
const handleEmail = createEmailHandler({
secret: env.CARLYEMAIL_WEBHOOK_SECRET,
async onEmail(event) {
console.log(event.message);
},
});Licence
MIT — see LICENSE. That covers this CLI only. The CarlyEmail service it
talks to is a separate, proprietary product; a permissive licence on an HTTP
client grants nothing over the API behind it.
