API Documentation

EmailAlias API

REST API for managing aliases, custom domains, and exposure intelligence.

Machine-readable spec: /openapi.json (OpenAPI 3 — for AI agents, code generators, and Postman/Insomnia imports).

API access is a Premium feature

API keys can only be created and used by Premium subscribers. Free accounts can still manage aliases through the web dashboard. Upgrade to Premium →

Quick Start

1

Create an API key

In the dashboard, go to Settings → API Keys and click Create. Copy the key immediately — it's only shown once.

2

Make your first request

Send the key as a Bearer token in the Authorization header.

curl -X POST https://emailalias.io/api/aliases \
  -H "Authorization: Bearer ea_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"alias_type": "random", "label": "Shopping"}'

Authentication

Every request must include a Bearer token in the Authorization header. API keys are prefixed with ea_live_, never expire, and are hashed at rest.

Authorization: Bearer ea_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Security: Keep your API key secret. If compromised, delete it from Settings → API Keys and create a new one. Each Premium account can hold up to 5 keys.

Rate Limits

Rate limit headers are returned on every response: X-RateLimit-Remaining, X-RateLimit-Reset.

LimitPremium
Requests per minute300
Emails sent per day350
Aliases created per day20
Total aliases (soft cap)150

Forwarding Destinations

Manage additional verified inboxes that aliases can forward to. Your primary account email is always available as a destination; up to 5 extras can be added.

GET/api/destinations

List the primary destination followed by any extras (verified or pending). The primary row has id=null and is_primary=true.

Response
[
  {
    "id": null,
    "email": "you@example.com",
    "verified": true,
    "is_primary": true,
    "created_at": "2026-03-01T12:00:00Z"
  },
  {
    "id": "b2c3d4e5-...",
    "email": "work@example.com",
    "verified": true,
    "is_primary": false,
    "created_at": "2026-04-10T09:24:11Z"
  },
  {
    "id": "c3d4e5f6-...",
    "email": "newsletter@example.com",
    "verified": false,
    "is_primary": false,
    "created_at": "2026-04-23T08:40:02Z"
  }
]
POST/api/destinations

Add a new destination. Sends a verification email to the address. The destination is not usable until verified by clicking the link (24h expiry).

Request Body
{ "email": "work@example.com" }
Response
{
  "id": "b2c3d4e5-...",
  "email": "work@example.com",
  "verified": false,
  "is_primary": false,
  "created_at": "2026-04-23T08:40:02Z"
}
POST/api/destinations/{destination_id}/resend

Regenerate the verification token and resend the verification email.

Response
{
  "id": "b2c3d4e5-...",
  "email": "work@example.com",
  "verified": false,
  "is_primary": false,
  "created_at": "2026-04-23T08:40:02Z"
}
DELETE/api/destinations/{destination_id}

Remove a destination. Returns 409 if any alias still forwards to it — reassign those aliases first.

Response
// 204 No Content

Aliases

Create, list, update, and delete email aliases.

GET/api/aliases

List all aliases belonging to the authenticated user.

Response
[
  {
    "id": "5e2a7d8c-0f3b-4a1e-9c2d-4a6b8f9e1a7c",
    "alias_code": "x7k9m",
    "alias_email": "x7k9m@email91.com",
    "destination_email": "you@example.com",
    "active": true,
    "label": "Shopping",
    "display_name": "Sam Carter",
    "display_name_pending": null,
    "display_name_pending_since": null,
    "created_at": "2026-04-20T10:15:32Z"
  }
]
POST/api/aliases

Create a new alias. All fields except alias_type are optional. destination_email must be your primary address or a verified forwarding destination — omit to use primary. display_name is shown as the sender on outbound mail ("Name <addr>"); first-time set on creation activates immediately, subsequent edits go through the dedicated /display-name endpoint with a 24h cooldown.

Request Body
{
  "alias_type": "random",
  "label": "Shopping",
  "domain": "email91.com",
  "destination_email": "secondary@example.com",
  "display_name": "Sam Carter"
}
Response
{
  "id": "5e2a7d8c-0f3b-4a1e-9c2d-4a6b8f9e1a7c",
  "alias_code": "x7k9m",
  "alias_email": "x7k9m@email91.com",
  "destination_email": "you@example.com",
  "active": true,
  "label": "Shopping",
  "display_name": "Sam Carter",
  "display_name_pending": null,
  "display_name_pending_since": null,
  "created_at": "2026-04-23T08:42:11Z"
}
PATCH/api/aliases/{alias_id}

Toggle active state, update the label, or change the forwarding destination. To change the display name, use the dedicated /display-name endpoint instead — it has a 24h cooldown that this endpoint bypasses.

Request Body
{
  "active": false,
  "label": "Retired"
}
Response
{
  "id": "5e2a7d8c-0f3b-4a1e-9c2d-4a6b8f9e1a7c",
  "alias_code": "x7k9m",
  "alias_email": "x7k9m@email91.com",
  "destination_email": "you@example.com",
  "active": false,
  "label": "Retired",
  "display_name": "Sam Carter",
  "display_name_pending": null,
  "display_name_pending_since": null,
  "created_at": "2026-04-20T10:15:32Z"
}
PATCH/api/aliases/{alias_id}/display-name

Schedule a display-name change for the alias. Edits do NOT take effect immediately — the new value lands in display_name_pending and promotes to display_name 24 hours after the most recent edit. Editing again resets the clock to NOW. Capped at 3 edits per rolling 24h per alias. Sending null or an empty string clears the name (also 24h-delayed). Brand-impersonation patterns (PayPal, Apple, banks, etc.) are blocked after homoglyph and leetspeak normalisation. Returns 400 on validation failure, 429 when the rate limit is exhausted.

Request Body
{
  "display_name": "John Smith"
}
Response
{
  "id": "5e2a7d8c-0f3b-4a1e-9c2d-4a6b8f9e1a7c",
  "alias_code": "x7k9m",
  "alias_email": "x7k9m@email91.com",
  "destination_email": "you@example.com",
  "active": true,
  "label": "Shopping",
  "display_name": "Sam Carter",
  "display_name_pending": "John Smith",
  "display_name_pending_since": "2026-05-01T10:00:00Z",
  "created_at": "2026-04-20T10:15:32Z"
}
DELETE/api/aliases/{alias_id}

Permanently delete an alias. Returns 204 on success.

Response
// 204 No Content
GET/api/aliases/domains

List domains available for alias creation (system + your verified custom domains).

Response
[
  { "domain": "email91.com", "type": "system", "is_default": true },
  { "domain": "yourdomain.com", "type": "custom", "is_default": false }
]

Send Email

Send outbound mail from an alias. The recipient only sees the alias address.

POST/api/send-email

Send an email from one of your aliases.

Request Body
{
  "alias_id": "5e2a7d8c-0f3b-4a1e-9c2d-4a6b8f9e1a7c",
  "to_email": "recipient@example.com",
  "subject": "Hello",
  "body": "Sent from my alias.",
  "html_body": "<p>Sent from my alias.</p>"
}
Response
{
  "success": true,
  "message_id": "010001909f4e-...-000000@us-west-2.amazonses.com",
  "message": "Email sent successfully."
}

Custom Domains

Register and verify your own domains for branded aliases.

GET/api/domains

List all custom domains on the account, with each DNS check reported individually. `verified` flips to true once txt, mx, spf, dkim, and dmarc all pass. `mail_from_verified` is a separate, optional cosmetic upgrade (bounce.<domain> Return-Path) and does not gate `verified`.

Response
[
  {
    "id": "a1b2c3d4-5e6f-7a8b-9c0d-e1f2a3b4c5d6",
    "domain_name": "yourdomain.com",
    "verified": true,
    "verification_token": "emailalias-verify=...",
    "txt_verified": true,
    "mx_verified": true,
    "spf_verified": true,
    "dkim_verified": true,
    "dmarc_verified": true,
    "mail_from_verified": true,
    "alias_count": 12,
    "created_at": "2026-03-12T14:22:08Z",
    "required_dns_records": [
      { "type": "TXT", "name": "@", "value": "emailalias-verify=..." },
      { "type": "MX",  "name": "@", "value": "inbound-smtp.us-east-1.amazonaws.com", "priority": 10 }
    ]
  }
]
POST/api/domains

Register a new custom domain. Response includes the DNS records you must publish: ownership TXT, inbound MX, SPF/DMARC TXT, 3 DKIM CNAMEs, plus 2 optional records under bounce.<domain> for Custom MAIL FROM. The first 5 dimensions gate `verified`; MAIL FROM is opt-in and tracked separately as `mail_from_verified`.

Request Body
{ "domain_name": "yourdomain.com" }
Response
{
  "id": "a1b2c3d4-5e6f-7a8b-9c0d-e1f2a3b4c5d6",
  "domain_name": "yourdomain.com",
  "verified": false,
  "verification_token": "emailalias-verify=abc123...",
  "txt_verified": false,
  "mx_verified": false,
  "spf_verified": false,
  "dkim_verified": false,
  "dmarc_verified": false,
  "mail_from_verified": false,
  "alias_count": 0,
  "created_at": "2026-04-23T08:45:00Z",
  "required_dns_records": [
    { "type": "TXT",   "name": "@",                 "value": "emailalias-verify=abc123..." },
    { "type": "MX",    "name": "@",                 "value": "inbound-smtp.us-east-1.amazonaws.com", "priority": 10 },
    { "type": "TXT",   "name": "@",                 "value": "v=spf1 include:amazonses.com ~all" },
    { "type": "TXT",   "name": "_dmarc",            "value": "v=DMARC1; p=quarantine; rua=mailto:dmarc@emailalias.io" },
    { "type": "MX",    "name": "bounce",            "value": "feedback-smtp.us-east-1.amazonses.com", "priority": 10 },
    { "type": "TXT",   "name": "bounce",            "value": "v=spf1 include:amazonses.com ~all" },
    { "type": "CNAME", "name": "<dkim>._domainkey", "value": "<dkim>.dkim.amazonses.com" }
  ]
}
POST/api/domains/{domain_id}/verify

Re-check DNS. Domain becomes verified only when the 5 core checks pass (txt/mx/spf/dkim/dmarc). mail_from_verified is reported separately and does not gate the verified flag — including it would silently flip already-verified domains back to Pending after MAIL FROM was added as a feature.

Response
{
  "verified": true,
  "txt_verified": true,
  "mx_verified": true,
  "spf_verified": true,
  "dkim_verified": true,
  "dmarc_verified": true,
  "mail_from_verified": true,
  "message": "Domain fully verified. SPF, DKIM, and DMARC are all configured correctly.",
  "failed_checks": []
}
DELETE/api/domains/{domain_id}

Remove a domain. Aliases on this domain are also deleted. Returns 204 on success.

Response
// 204 No Content

Analytics

Dashboard stats, forwarding logs, and exposure events.

GET/api/analytics/dashboard

High-level counters for the authenticated user.

Response
{
  "total_aliases": 12,
  "active_aliases": 10,
  "emails_forwarded": 348,
  "emails_blocked": 21,
  "exposure_alerts": 2
}
GET/api/analytics/logs?page=1&per_page=25

Paginated email delivery log (inbound + outbound).

Response
{
  "items": [
    {
      "id": "c3d4e5f6-...",
      "alias_id": "5e2a7d8c-...",
      "alias_email": "x7k9m@email91.com",
      "sender_email": "noreply@shop.com",
      "recipient_email": "you@example.com",
      "subject": "Your order #1234",
      "received_at": "2026-04-23T07:12:45Z",
      "direction": "inbound",
      "status": "delivered",
      "block_reason": null
    }
  ],
  "total": 348,
  "page": 1,
  "per_page": 25,
  "pages": 14
}
GET/api/analytics/exposure?page=1&per_page=25

Aliases flagged with suspicious sender activity or leaked to third parties.

Response
{
  "items": [
    {
      "id": "e5f6a7b8-...",
      "alias_id": "5e2a7d8c-...",
      "alias_email": "x7k9m@email91.com",
      "sender_domain": "suspicious-marketer.com",
      "risk_score": 78,
      "detected_at": "2026-04-18T03:01:17Z"
    }
  ],
  "total": 2,
  "page": 1,
  "per_page": 25,
  "pages": 1
}

Errors

All errors use standard HTTP status codes and return a JSON body.

{
  "detail": "Human-readable error message"
}
CodeStatusDescription
400Bad RequestInvalid body or missing required fields.
401UnauthorizedMissing or invalid API key.
403ForbiddenAction not permitted (e.g. accessing an alias or domain you do not own).
404Not FoundThe requested resource does not exist.
409ConflictResource already exists (e.g. duplicate domain).
422Unprocessable EntityValidation failed on the request body.
429Too Many RequestsRate limit exceeded. Retry after X-RateLimit-Reset.
500Internal Server ErrorUnexpected error. Contact support if it persists.

cURL Cookbook

Copy-paste recipes for every endpoint. Set EMAILALIAS_API_KEY in your shell first:

export EMAILALIAS_API_KEY="ea_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Aliases

GET/api/aliases
curl https://emailalias.io/api/aliases \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY"
POST/api/aliases
curl -X POST https://emailalias.io/api/aliases \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"alias_type":"random","label":"Shopping"}'
POST/api/aliases
curl -X POST https://emailalias.io/api/aliases \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"alias_type":"custom","custom_code":"work-signup","label":"Work","destination_email":"work@example.com"}'
POST/api/aliases
curl -X POST https://emailalias.io/api/aliases \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"alias_type":"random","display_name":"Sam Carter"}'
PATCH/api/aliases/{id}/display-name
# Schedules the change. Goes live 24h after this edit.
# Limit: 3 edits per rolling 24h per alias. Brand impersonation
# (PayPal, Apple, banks, etc.) is rejected with 400.
curl -X PATCH https://emailalias.io/api/aliases/ALIAS_ID/display-name \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"display_name":"John Smith"}'
PATCH/api/aliases/{id}/display-name
# Clearing also goes through cooldown — blocks the
# "set brand → blast → clear" abuse cycle.
curl -X PATCH https://emailalias.io/api/aliases/ALIAS_ID/display-name \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"display_name":null}'
PATCH/api/aliases/{id}
curl -X PATCH https://emailalias.io/api/aliases/ALIAS_ID \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"active":false}'
DELETE/api/aliases/{id}
curl -X DELETE https://emailalias.io/api/aliases/ALIAS_ID \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY"
GET/api/aliases/domains
curl https://emailalias.io/api/aliases/domains \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY"

Forwarding Destinations

GET/api/destinations
curl https://emailalias.io/api/destinations \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY"
POST/api/destinations
curl -X POST https://emailalias.io/api/destinations \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"work@example.com"}'
POST/api/destinations/{id}/resend
curl -X POST https://emailalias.io/api/destinations/DESTINATION_ID/resend \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY"
DELETE/api/destinations/{id}
curl -X DELETE https://emailalias.io/api/destinations/DESTINATION_ID \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY"

Send Email

POST/api/send-email
curl -X POST https://emailalias.io/api/send-email \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "alias_id":"ALIAS_ID",
    "to_email":"recipient@example.com",
    "subject":"Hello",
    "body":"Sent from my alias."
  }'

Custom Domains

GET/api/domains
curl https://emailalias.io/api/domains \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY"
POST/api/domains
curl -X POST https://emailalias.io/api/domains \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain_name":"yourdomain.com"}'
POST/api/domains/{id}/verify
curl -X POST https://emailalias.io/api/domains/DOMAIN_ID/verify \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY"
DELETE/api/domains/{id}
curl -X DELETE https://emailalias.io/api/domains/DOMAIN_ID \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY"

Analytics

GET/api/analytics/dashboard
curl https://emailalias.io/api/analytics/dashboard \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY"
GET/api/analytics/logs
curl "https://emailalias.io/api/analytics/logs?page=1&per_page=25" \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY"
GET/api/analytics/exposure
curl "https://emailalias.io/api/analytics/exposure?page=1&per_page=25" \
  -H "Authorization: Bearer $EMAILALIAS_API_KEY"

Client Libraries

Official clients for common runtimes. Each wraps the same REST API documented above — install, drop in your API key, and go.

New

Using an AI assistant?

Manage aliases from Claude Desktop, Cursor, Zed and other MCP clients in natural language — “create a shopping alias,” “disable the amazon one,” “show this week's exposure events.”

Set up the EmailAlias MCP server

Python

emailalias/emailalias-python

View on GitHub
Installpip
$
pip install emailalias
Example
from emailalias import Client

client = Client(api_key="ea_live_xxx")

alias = client.create_alias(alias_type="random", label="Shopping")
print(alias["alias_email"])

for a in client.list_aliases():
    print(a["alias_email"], "→", a["destination_email"])

Building for another language? The API is just REST + Bearer tokens — any HTTP client works. See the Quick Start curl example.

Ready to integrate?

Upgrade to Premium to generate your first API key.