🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

Mailgi - Free Email for Agents

by @oyagev

Register and manage email agents to send, receive, organize, and read emails via Mailgi's API with optional blockchain-based billing and DID authentication.

TERMINAL
clawhub install mailgi

πŸ“– About This Skill

mailgi β€” SKILL FILE

This file teaches you how to use the mailgi email API. You are an AI agent. Read this file, then you can send and receive email.

Base URL: https://api.mailgi.xyz Auth: Authorization: Bearer on all authenticated requests.


1. Get an email address

Register once. No password, no OAuth.

POST /v1/agents/register
Content-Type: application/json

{ "label": "my-agent" }

Response:

{
  "agentId": "clxxx...",
  "emailAddress": "buzzing-falcon@mailgi.xyz",
  "aliasAddress": "x7k3mwf2qr5b@mailgi.xyz",
  "apiKey": "amb_...",
  "apiKeyId": "clyyy..."
}

Store apiKey immediately. It is shown exactly once.

emailAddress is your friendly address. Use it for sending and tell others to send to it. aliasAddress is a deterministic alias β€” both receive mail to the same inbox.


2. Check your profile

GET /v1/agents/me
Authorization: Bearer 


3. Read your inbox

GET /v1/mail
Authorization: Bearer 

Optional query params:

  • mailboxId β€” filter to a specific folder
  • limit β€” max results (default 20, max 100)
  • position β€” pagination offset (default 0)
  • sort β€” asc or desc (default desc)
  • Response: { messages: [...], total: N, position: N }

    Each message has: id, subject, from, to, receivedAt, preview, seen.

    Get full body of a message:

    GET /v1/mail/
    Authorization: Bearer 
    

    Response includes htmlBody and/or textBody. If body is a string, use it directly. If it is an array of JMAP parts, look up bodyValues[part.partId].value for the text.


    4. Send email

    POST /v1/mail/send
    Authorization: Bearer 
    Content-Type: application/json

    { "to": ["someone@example.com"], "subject": "Hello from my agent", "textBody": "Hi there." }

    Optional fields: cc, bcc, htmlBody, replyTo. to, cc, bcc accept a single string or an array of strings.

    Response: { "messageId": "..." }

    Sending is free. Rate limit: 100 external emails per day per API key.


    5. Manage mailboxes (folders)

    List folders:

    GET /v1/mailboxes
    Authorization: Bearer 
    

    Each mailbox has id, name, role (inbox/sent/trash/drafts/etc), totalEmails, unreadEmails.

    Create a folder:

    POST /v1/mailboxes
    Authorization: Bearer 
    Content-Type: application/json

    { "name": "Projects", "parentId": "" }

    Move a message to a folder:

    PATCH /v1/mail//move
    Authorization: Bearer 
    Content-Type: application/json

    { "mailboxId": "" }

    Mark as read:

    PATCH /v1/mail//flags
    Authorization: Bearer 
    Content-Type: application/json

    { "seen": true }

    Delete a message (moves to Trash):

    DELETE /v1/mail/
    Authorization: Bearer 
    


    6. API keys

    You can create additional API keys (e.g. one per task):

    POST /v1/apikeys
    Authorization: Bearer 
    Content-Type: application/json

    { "label": "task-runner", "expiresAt": "2026-12-31T00:00:00Z" }

    Response includes apiKey (raw, shown once) and id.

    List keys: GET /v1/apikeys Revoke a key: DELETE /v1/apikeys/


    7. DID-based auth (optional)

    If you registered with a did:key: DID, you can authenticate without an API key:

    1. Request a challenge:

    POST /v1/auth/challenge
    Content-Type: application/json

    { "did": "did:key:z6Mk..." }

    2. Sign the returned nonce with your Ed25519 private key (base64url), then verify:

    POST /v1/auth/verify
    Content-Type: application/json

    { "did": "did:key:z6Mk...", "nonce": "...", "signature": "" }

    Response: { "token": "...", "expiresIn": 3600 } β€” use as Authorization: Bearer .


    8. Error responses

    All errors follow:

    { "error": { "code": "ERROR_CODE", "message": "Human-readable description" } }
    

    Common codes:

  • 401 β€” missing or invalid API key
  • 404 β€” message or mailbox not found
  • 409 β€” conflict (e.g. mailbox name already exists)
  • 429 β€” rate limit exceeded

  • 9. Health

    GET /health        β€” liveness (always 200 if server is up)
    GET /health/ready  β€” readiness (checks DB + mail server)
    


    Quick start (copy-paste)

    # 1. Register
    RESP=$(curl -s -X POST https://api.mailgi.xyz/v1/agents/register \
      -H "Content-Type: application/json" \
      -d '{"label":"my-agent"}')
    EMAIL=$(echo $RESP | jq -r .emailAddress)
    KEY=$(echo $RESP | jq -r .apiKey)

    2. Send a message

    curl -s -X POST https://api.mailgi.xyz/v1/mail/send \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d "{\"to\":[\"someone@example.com\"],\"subject\":\"Hi\",\"textBody\":\"Hello from $EMAIL\"}"

    3. Read inbox

    curl -s https://api.mailgi.xyz/v1/mail \ -H "Authorization: Bearer $KEY"


    10. TypeScript / Node.js SDK

    Install:

    npm install @mailgi/mailgi
    

    import { AgentMailboxClient } from '@mailgi/mailgi';

    // Construct from a stored API key const client = AgentMailboxClient.withApiKey( 'https://api.mailgi.xyz', process.env.MAILGI_API_KEY!, );

    // Register a new agent (first time only β€” save the returned apiKey) const reg = await client.agents.register({ label: 'my-agent' }); // reg.emailAddress => 'buzzing-falcon@mailgi.xyz' // reg.apiKey => 'amb_...' (shown once β€” store it) client.apiKey = reg.apiKey;

    // Send email const { messageId } = await client.mail.send({ to: ['someone@example.com'], subject: 'Hello', textBody: 'Hi from my agent.', });

    // Read inbox const { messages } = await client.mail.list({ limit: 20, sort: 'desc' }); const email = await client.mail.get(messages[0].id); console.log(email.subject, email.textBody);

    // Mark as read await client.mail.setFlags(email.id, { seen: true });

    All SDK methods map 1-to-1 to the REST endpoints above. Errors extend AgentMailboxError with statusCode and code:

    import { NotFoundError, UnauthorizedError } from '@mailgi/mailgi';

    try { await client.mail.get('bad-id'); } catch (err) { if (err instanceof NotFoundError) console.error('Not found'); if (err instanceof UnauthorizedError) console.error('Bad API key'); }


    11. CLI

    Install globally:

    npm install -g @mailgi/mailgi
    

    All commands require --agent .

    # Register a new agent (saves API key to ~/.mailgi/config.json)
    mailgi register --label my-agent --agent me@mailgi.xyz

    Save an existing agent by API key

    mailgi login --agent buzzing-falcon@mailgi.xyz --apikey amb_...

    List saved agents

    mailgi agents

    Show agent profile (live from API)

    mailgi me --agent buzzing-falcon

    Read inbox

    mailgi inbox --agent buzzing-falcon mailgi inbox --agent buzzing-falcon --limit 50

    Read a message (auto-marks as seen)

    mailgi read --agent buzzing-falcon

    Send email

    mailgi send --agent buzzing-falcon --to alice@example.com --subject "Hi" --body "Hello" mailgi send --agent buzzing-falcon --to alice@example.com --subject "Hi" --body-file ./message.txt

    Delete a message

    mailgi delete --agent buzzing-falcon

    Mailboxes

    mailgi mailboxes --agent buzzing-falcon mailgi mailboxes create "Projects" --agent buzzing-falcon mailgi mailboxes delete --agent buzzing-falcon

    API keys

    mailgi keys --agent buzzing-falcon mailgi keys create --label task-key --agent buzzing-falcon mailgi keys revoke --agent buzzing-falcon

    Config

    mailgi config show mailgi config set-url https://api.mailgi.xyz

    Remove saved agent

    mailgi logout --agent buzzing-falcon --yes

    Raw JSON output (any command)

    mailgi inbox --agent buzzing-falcon --json


    Full interactive docs: https://api.mailgi.xyz/docs Machine-readable spec: https://api.mailgi.xyz/openapi.json


    Support

    Questions or issues? Email objective-crocodile@mailgi.xyz β€” yes, it's a real mailgi inbox.