maeil API & MCP

Send a calendar invite to someone using only their phone number, and get back their real email once they accept — over a REST API, an MCP server, or a tokenized email address. This is the reference for all three.

MCP & API access is a paid entitlement — included from the Power plan up. Personal use on Power; automation, webhooks, and metered volume on Builder and Business. The Free and Standard plans do not include API or MCP access. See pricing. Trying to mint a key on a plan without access returns 403 api_requires_paid_plan.

On this page

Overview

Every integration path drives the same core action: a phone-only person is invited to an event, maeil texts them a one-tap accept link, and on accept their real email is returned to you.

SurfaceBaseBest for
REST APIhttps://api.maeil.ccAny backend, CRM, or platform that can make an HTTPS request.
MCP serverhttps://mcp.maeil.cc/mcpAI assistants and agents (Claude, Cursor, VS Code, ChatGPT, …).
Email on-ramp<phone>+<token>@maeil.ccEmail-based schedulers (Cal.com, Howie, …) — zero code.

All three authenticate with the same mk_live_… API key. US phone numbers only in v1.

Authentication

Two credentials, two purposes:

CredentialUsed forHow you get it
API key mk_live_…The send endpoints (/v1/invite) and MCPMinted from your account — POST /v1/keys
Account JWTKey management (/v1/keys)POST /auth/login with your account email + password

Send the API key as either header:

Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-API-Key: mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
The raw key is shown once, at creation. maeil stores only its SHA-256 hash and a short display prefix — a key can never be retrieved again. If you lose one, revoke it and mint a new one.

Create an API key

POST/v1/keys

Authenticate with your account JWT (not an API key). Max 20 active keys per account.

curl -X POST https://api.maeil.cc/v1/keys \
  -H "Authorization: Bearer <YOUR_ACCOUNT_JWT>" \
  -H "Content-Type: application/json" \
  -d '{"name":"production key"}'

Response — 201 Created:

{
  "id": "key_...",
  "key": "mk_live_...",                     // shown ONCE — copy it now
  "prefix": "mk_live_abcd",
  "name": "production key",
  "email_token": "mp_Xa9kQ3v7bN2pLw...",    // handle for the email on-ramp
  "email_address_template": "<phone>+mp_Xa9kQ3v7bN2pLw...@maeil.cc"
}

GET /v1/keys lists your keys (prefix, name, per-key limits, usage, revoked flag). DELETE /v1/keys/:id revokes one. Revoking a key also disables its email on-ramp address immediately.

Send an invite

POST/v1/invite

Authenticate with an API key.

curl -X POST https://api.maeil.cc/v1/invite \
  -H "Authorization: Bearer mk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+15551234567",
    "event": {
      "title": "Intro call",
      "start": "2026-06-15T14:00:00Z",
      "end": "2026-06-15T14:30:00Z",
      "location": "Phone",
      "description": "Quick 15-min intro",
      "organizer": "agent@firm.com"
    },
    "consent_attestation": "client booked by phone on 6/1",
    "callback_url": "https://your-app.example/webhooks/maeil"
  }'
FieldRequiredNotes
phoneyesUS only. Accepts +15551234567, 15551234567, 5551234567, (555) 123-4567.
event.titleyesShown in the SMS and on the accept page.
event.startyesISO 8601 datetime (…T14:00:00Z or with offset), or a date (2026-06-15) for all-day.
event.organizeryesValid email. The RSVP and the revealed email are delivered here.
consent_attestationyestrue, or a string describing the basis (e.g. "met at conference, gave card"). Stored on the invite and in the audit log. See Compliance.
event.endnoSame formats as start.
event.locationnoFree text.
event.descriptionnoFree text.
callback_urlnoPublic https:// URL for the response webhook. Requires the Builder plan (else 403 webhook_requires_builder). Private/loopback hosts are rejected (SSRF guard).

Response — 201 Created:

{ "invite_id": "V1StGXR8_Z5jdHi6B-myT", "status": "sent", "accept_url": "https://app.maeil.cc/a/V1StGXR8_Z5jdHi6B-myT" }
The SMS copy is always built from maeil's locked transactional template. You supply event data only — never message text — so an invite can't be turned into a marketing message.

Get invite status

GET/v1/invite/:id

Status and the revealed email once accepted. Scoped to the key's owning account.

{
  "invite_id": "V1StGXR8_Z5jdHi6B-myT",
  "status": "accepted",
  "recipient_phone": "+15551234567",
  "event_title": "Intro call",
  "event_start": "2026-06-15T14:00:00Z",
  "revealed_email": "dana@example.com",
  "created_at": "2026-05-24T16:00:00.000Z"
}

status is one of sent · accepted · declined · failed. revealed_email is null until the recipient accepts and provides it.

Webhooks

If you passed callback_url (Builder plan), maeil POSTs it when the recipient responds:

{
  "invite_id": "V1StGXR8_Z5jdHi6B-myT",
  "status": "accepted",
  "recipient_email": "dana@example.com",
  "recipient_phone": "+15551234567",
  "event_title": "Intro call",
  "responded_at": "2026-05-24T17:02:11.000Z"
}

Verify the signature. Each callback carries X-Maeil-Signature: sha256=<hex> — an HMAC-SHA256 of the exact request body. Recompute it with your shared secret and compare before trusting the payload.

Fire-and-forget (v1): there is no delivery retry and no idempotency key yet — each POST /v1/invite creates a new invite and consumes one unit of quota. Don't blind-retry a send; reconcile with GET /v1/invite/:id first. callback_url must be a public https host — localhost, dotless, .local/.internal, and private/loopback/link-local/CGNAT IPs are rejected at create time.

Error codes

Errors return a JSON body { "error": "...", "code": "..." } with the HTTP status below.

StatuscodeMeaning & what to do
400consent_requiredconsent_attestation was missing or false. Add it and resend.
400Invalid phone, missing/invalid event field, bad date, non-https callback_url, or malformed JSON. Fix the payload.
401Missing, invalid, or revoked API key. Check the Authorization header.
403suppressedRecipient texted STOP and is globally opted out. Never re-texted; do not retry.
403webhook_requires_builderYou passed callback_url without webhook access (Builder or Business required). Upgrade, or drop the callback and poll status.
403api_requires_paid_planAPI/MCP access needs the Power plan or above (Power, Builder, Business; returned when minting a key). Upgrade the account.
409key_limit_reachedYou already have 20 active keys. Revoke an unused one first.
429rate_limitedThis key's — or your plan's daily — send cap is reached. Back off and retry later.
429fair_use_reachedYour plan's monthly fair-use pool is exhausted (non-metered plans). Upgrade to Builder for metered volume.
502Invite stored, but the carrier rejected the SMS. Response is { status: "failed", … }.

Rate limits

Two layers enforce sending: a per-key cap and your account's monthly pool. API keys draw from the same account pool as the dashboard and your personal MCP key — one total, not split.

PlanDailyMonthly (included)Above included
Free33 to start, then 1 / month
Standard25150hard cap
Power40250hard cap
Builder2001,500metered — $0.05 / send
Business4005,000metered — $0.05 / send

New keys are seeded with per-key limits derived from your plan. On metered plans (Builder, Business) the per-key monthly bound is effectively unlimited so a single key can reach your full included volume and beyond — the account pool plus metered overage governs. Hitting a limit returns 429 (rate_limited for a daily/per-key cap, fair_use_reached for a non-metered monthly pool).

MCP server

maeil runs as a Model Context Protocol server so assistants can invite by phone and read back the email without leaving the conversation. It exposes exactly two tools:

ToolArgumentsReturns
send_calendar_invitephone, title, start, organizer, consent_attestation (all required); end, location, description, callback_url (optional)The invite_id and initial status.
get_invite_statusinvite_id (required)Status (sent/accepted/declined/failed) and the revealed email once accepted.

Protocol revision 2025-06-18 (Streamable HTTP). consent_attestation is required at the tool — a send without it is refused before any network call.

Remote endpoint

Any client that can attach an Authorization header can point at https://mcp.maeil.cc/mcp:

{
  "mcpServers": {
    "maeil": {
      "url": "https://mcp.maeil.cc/mcp",
      "headers": { "Authorization": "Bearer mk_live_REPLACE_ME" }
    }
  }
}

Local connector

For clients that can't set a header (or require OAuth), launch the bundled Node connector with your key in the environment (Node 18+). Example — Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "maeil": {
      "command": "node",
      "args": ["/path/to/workers/mcp/dist/maeil-mcp-stdio.mjs"],
      "env": { "MAEIL_API_KEY": "mk_live_REPLACE_ME" }
    }
  }
}

Discover the tools over plain JSON-RPC:

curl -s https://mcp.maeil.cc/mcp \
  -H "Authorization: Bearer mk_live_REPLACE_ME" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Client compatibility. Claude (Desktop & web), Cursor, and VS Code work today — via the local connector, or by attaching the Authorization header shown above. ChatGPT's web connector may require an OAuth 2.1 flow and can refuse a static Bearer key (OAuth is a planned follow-up); until then, connect ChatGPT through the local connector. Any client that can send a Bearer header works against the remote endpoint.

Email on-ramp

An email-based scheduler (Cal.com, Howie, Reclaim, Motion, …) can use maeil with zero code — just add a tokenized maeil address as a guest on the calendar invite:

<phone>+<email_token>@maeil.cc
e.g.  5551234567+mp_Xa9kQ3v7bN2pLw...@maeil.cc

The plain consumer address 5551234567@maeil.cc is unrelated; the token is what attributes a send to a contracted partner. Revoke the key and the address stops resolving immediately.

Compliance

Questions the docs don't answer? Email maeil@cb-industries.co or visit Help.