โœ‰ Maily Developer Hub
โ† Dashboard

Docker & Installation Guide

Run Maily via official pre-built Docker containers, Docker Compose, or develop locally from source.

Option A โ€” Docker (Pre-built Image, No Git Clone Required)
docker run -d \
        --name maily \
        -p 8477:8477 \
        -e MAILY_API_KEY=your_secret_api_key \
        -v maily_data:/data \
        --restart unless-stopped \
        ghcr.io/stormsia/maily:latest
2. Using Docker Compose
# Linux / macOS (curl)
      curl -O https://raw.githubusercontent.com/stormsia/maily/main/docker-compose.prod.yml
      docker compose -f docker-compose.prod.yml up -d

      # Windows (PowerShell)
      Invoke-WebRequest -Uri https://raw.githubusercontent.com/stormsia/maily/main/docker-compose.prod.yml -OutFile docker-compose.prod.yml
      docker compose -f docker-compose.prod.yml up -d
Updating the Pre-built Image
# If using Docker Compose:
      docker compose -f docker-compose.prod.yml pull
      docker compose -f docker-compose.prod.yml up -d

      # If using docker run:
      docker pull ghcr.io/stormsia/maily:latest
      docker stop maily && docker rm maily
      docker run -d --name maily -p 8477:8477 -e MAILY_API_KEY=your_secret_api_key -v maily_data:/data --restart unless-stopped ghcr.io/stormsia/maily:latest
Option B โ€” Build from Source / Development
git clone https://github.com/stormsia/maily.git && cd maily
      cp .env.example .env        # adjust if needed
      docker compose up -d --build
2. Local Python (Python 3.13+)
pip install maily-server
      maily
From Git Source (Development)
git clone https://github.com/stormsia/maily.git && cd maily
      python -m venv .venv
      # Windows:
      .venv\Scripts\activate
      # Linux / macOS:
      source .venv/bin/activate

      pip install -e ".[dev]"
      maily

Standalone Python Async Client

maily-client is a zero-dependency (only httpx, loguru, pydantic) single-file client. Full async support, typed pydantic models, granular exception hierarchy, and high-level verification helpers.

Installation
pip install maily-client
Alternative options:
# Directly from GitHub repository:
      pip install "maily-client @ git+https://github.com/stormsia/maily.git#subdirectory=client"

      # Or download the zero-dependency single-file client directly:
      curl -O https://raw.githubusercontent.com/stormsia/maily/main/client/maily_client.py
1-Line Mailbox & Verification Code Extraction
import asyncio

      # Preferred when using maily-client SDK:
      from maily_client import Maily, WaitTimeoutError
      # (Or if using the maily-server package directly: from maily import Maily, WaitTimeoutError)


      async def main():
          async with Maily("http://127.0.0.1:8477", api_key="your_secret_api_key") as api:
              # Create a disposable mailbox with automatic domain selection
              box = await api.create_temp_mailbox()
              print(f"Disposable email: {box.address}")

              # Send an email to box.address, then wait for the code:
              try:
                  code = await api.wait_for_code(box, timeout=60.0)
                  print(f"Received verification code: {code}")
              except WaitTimeoutError:
                  print("Timed out waiting for email.")


      asyncio.run(main())
Confirmation Extraction (ExtractedConfirmation Dual Access)
async with Maily("http://127.0.0.1:8477") as api:
          box = await api.create_temp_mailbox()

          # Wait for email and extract both verification code and activation link
          res = await api.wait_for_confirmation_email(box, digit_code_mask=r"\b(\d{6})\b")
          if res is not None:
              # 1. Attribute / dot access:
              print(f"Subject: {res.subject}")
              print(f"Code: {res.confirmation_code}")

              # 2. Key mapping access:
              print(f"Link: {res['confirmation_link']}")
              print(f"Code: {res['confirmation_code']}")
Synchronous Client (SyncMaily)
# Standalone client:
      from maily_client import SyncMaily
      # Or via server package: from maily import SyncMaily

      with SyncMaily("http://127.0.0.1:8477", api_key="your_secret_api_key") as api:
          box = api.create_temp_mailbox()
          code = api.wait_for_code(box, timeout=45.0)
          print(f"Received code synchronously: {code}")
Confirmation Link Extraction
async with Maily("http://127.0.0.1:8477") as api:
          box = await api.create_temp_mailbox(provider="tempmail.plus")
          print(f"Address: {box.address}")

          # Automatically extracts URLs containing 'confirm', 'verify', or 'activate'
          link = await api.wait_for_link(box, timeout=90.0)
          print(f"Confirmation URL: {link}")
Real-Time Email Streaming (Async Iterator)
async with Maily("http://127.0.0.1:8477") as api:
          box = await api.mailboxes.create(domain="mail.tm")

          # Continuously yields new messages as they arrive
          async for msg in api.messages.iter_messages(box, poll_interval=2.0, timeout=120.0):
              print(f"Incoming: {msg.subject} from {msg.from_address}")
              detail = await api.messages.get(box, msg.message_api_id)
              print("Body text:\n", detail.text)
Granular Exception Handling
from maily_client import (
          AuthenticationError,  # 401: Invalid API key
          ForbiddenError,  # 403: Scope denied
          NotFoundError,  # 404: Mailbox or message missing
          ValidationError,  # 422 / 400: Bad params
          RateLimitError,  # 429: Rate limited
          UpstreamProviderError,  # 502/504: Provider downtime
          WaitTimeoutError,  # Timeout waiting for email/code
      )

      try:
          detail = await api.messages.get(box, "unknown_id")
      except NotFoundError:
          print("Message not found!")
      except AuthenticationError:
          print("Check your API key!")
Scoped API Key Management
async with Maily("http://127.0.0.1:8477", api_key="root_secret") as api:
          # Create key restricted to mail.tm only
          new_key = await api.keys.create(
              name="bot-worker",
              scope={"providers": ["mail.tm"], "parent_ids": [], "actions": ["read", "write"]},
          )
          print(f"Key Prefix: {new_key.prefix} | Secret: {new_key.secret}")
Retention & Database Maintenance
async with Maily("http://127.0.0.1:8477", api_key="root_secret") as api:
          # Run regular retention sweep (triggers DB maintenance if interval has elapsed)
          res = await api.admin.retention_run()
          print(f"Deleted: {res.mailboxes_deleted} boxes, {res.messages_deleted} messages")

          # Or force immediate full database maintenance (VACUUM, REINDEX, ANALYZE):
          res = await api.admin.retention_run(force_maintenance=True)

Active Provider Adapters

Maily seamlessly aggregates public temporary-mail networks and private Gmail parent accounts into one unified REST API.

Provider Target Kind Custom Username Custom Domain Passwords / PIN Raw Source Read Tracking
mail.tm domain Supported Supported Supported Supported Supported
tempmail.plus domain Supported Supported PIN Code Supported Supported
temp-mail.io domain Supported Supported No Supported No-op
noopmail.org domain Supported Supported No Supported No-op
anonymmail.net domain Supported Supported No Supported No-op
dispoemail.org domain Supported Supported No Supported No-op
temp.tf domain Supported Supported No Supported No-op
gmail parent +alias Own Account OAuth2 Supported Supported

Adding a New Provider?

See the developer guide in docs/ADDING_PROVIDERS.md for a complete step-by-step walkthrough and copy-pasteable boilerplate for adding any new email service with hermetic unit tests.

cURL & Authentication Cheat Sheet

Authenticate all requests with the X-API-Key HTTP header.

List available targets (domains)
curl -X GET "${window.location.origin}/v1/targets" \
        -H "X-API-Key: ${getSavedKey() || 'YOUR_API_KEY'}" \
        -H "Accept: application/json"
Create disposable mailbox
curl -X POST "${window.location.origin}/v1/mailboxes" \
        -H "X-API-Key: ${getSavedKey() || 'YOUR_API_KEY'}" \
        -H "Content-Type: application/json" \
        -d '{"provider": "mail.tm", "domain": "mail.tm"}'
Fetch messages for mailbox
curl -X GET "${window.location.origin}/v1/mailboxes/1/messages" \
        -H "X-API-Key: ${getSavedKey() || 'YOUR_API_KEY'}"
Inspect single message (text & HTML)
curl -X GET "${window.location.origin}/v1/mailboxes/1/messages/MESSAGE_ID" \
        -H "X-API-Key: ${getSavedKey() || 'YOUR_API_KEY'}"
Create scoped API key
curl -X POST "${window.location.origin}/v1/keys" \
        -H "X-API-Key: ${getSavedKey() || 'YOUR_API_KEY'}" \
        -H "Content-Type: application/json" \
        -d '{"name": "worker", "scope": {"providers": ["mail.tm"], "actions": ["read", "write"]}}'
Run retention sweep & database maintenance
curl -X POST "${window.location.origin}/v1/admin/retention/run?force_maintenance=true" \
        -H "X-API-Key: ${getSavedKey() || 'YOUR_API_KEY'}"