openapi: "3.1.0"
info:
  title: Dead Simple Email API
  version: 0.1.0
  description: |
    Email API for AI agents. Create inboxes, send and receive email,
    manage threads, and get real-time webhook notifications.

    ## Authentication
    All authenticated endpoints use Bearer token auth:
    ```
    Authorization: Bearer dse_your_api_key
    ```

    ## Idempotency
    POST endpoints accept an `Idempotency-Key` header. Same key within 24h
    returns the cached response without re-executing.

    ## Rate Limits
    Every response includes rate limit headers:
    - `X-RateLimit-Limit` — requests per window
    - `X-RateLimit-Remaining` — remaining requests
    - `X-RateLimit-Reset` — window reset timestamp
  contact:
    url: https://deadsimple.email
  license:
    name: MIT

servers:
  - url: https://api.deadsimple.email
    description: Production

security:
  - BearerAuth: []

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: API key (dse_...) or session token (JWT)

  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              example: validation_error
            message:
              type: string
              example: Email is required
            details:
              type: array
              items:
                type: object
        meta:
          $ref: "#/components/schemas/Meta"

    Meta:
      type: object
      properties:
        timestamp:
          type: string
          format: date-time
        request_id:
          type: string
          format: uuid

    Inbox:
      type: object
      properties:
        inbox_id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        local_part:
          type: string
        domain:
          type: string
        display_name:
          type: string
        tags:
          type: array
          items:
            type: string
        status:
          type: string
          enum: [active, paused]
        message_count:
          type: integer
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    Message:
      type: object
      properties:
        message_id:
          type: string
          format: uuid
        inbox_id:
          type: string
          format: uuid
        thread_id:
          type: string
          format: uuid
        direction:
          type: string
          enum: [inbound, outbound]
        from_email:
          type: string
          format: email
        to:
          type: array
          items:
            type: string
            format: email
        cc:
          type: array
          items:
            type: string
        subject:
          type: string
        labels:
          type: array
          items:
            type: string
          description: User-defined labels for organizing and filtering messages
        text_body:
          type: string
        html_body:
          type: string
        latest_reply:
          type: string
        tracking_id:
          type: string
          format: uuid
          description: Present when open/click tracking is enabled for this message
        attachments:
          type: array
          items:
            $ref: "#/components/schemas/Attachment"
        status:
          type: string
          enum: [sent, received, bounced, scheduled]
        is_spam:
          type: boolean
          description: Whether the message was detected as spam (inbound only)
        spam_score:
          type: number
          description: Spam confidence score from Rspamd/SpamAssassin (5.0+ = spam)
        created_at:
          type: string
          format: date-time

    TrackingStats:
      type: object
      properties:
        message_id:
          type: string
          format: uuid
        opens:
          type: integer
        clicks:
          type: integer
        last_opened_at:
          type: string
          format: date-time
          nullable: true
        last_clicked_at:
          type: string
          format: date-time
          nullable: true

    Attachment:
      type: object
      properties:
        attachment_id:
          type: string
          format: uuid
        filename:
          type: string
        content_type:
          type: string
        size:
          type: integer

    Thread:
      type: object
      properties:
        thread_id:
          type: string
          format: uuid
        subject:
          type: string
        message_count:
          type: integer
        participants:
          type: array
          items:
            type: string
        latest_message:
          type: object
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    Webhook:
      type: object
      properties:
        webhook_id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
            enum: [message.received, message.sent, message.delivered, message.bounced, message.complained, message.rejected, message.opened, message.link_clicked, inbox.created, inbox.deleted, domain.verified]
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time

    Domain:
      type: object
      properties:
        domain_id:
          type: string
          format: uuid
        domain:
          type: string
        status:
          type: string
          enum: [pending, verified, active]
        dns_records:
          type: array
          items:
            type: object
            properties:
              type:
                type: string
              name:
                type: string
              value:
                type: string
              purpose:
                type: string
        created_at:
          type: string
          format: date-time

paths:
  /v1/health:
    get:
      summary: Health check
      security: []
      tags: [System]
      responses:
        "200":
          description: API is healthy

  /v1/auth/signup:
    post:
      summary: Create an account
      security: []
      tags: [Auth]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
                  minLength: 8
                name:
                  type: string
            example:
              email: dev@example.com
              password: securepass123
              name: Alex
      responses:
        "201":
          description: Account created, verification email sent
        "400":
          description: Validation error
        "409":
          description: Email already registered

  /v1/auth/verify:
    post:
      summary: Verify email with code
      security: []
      tags: [Auth]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, code]
              properties:
                email:
                  type: string
                code:
                  type: string
      responses:
        "201":
          description: Email verified, session token and API key returned

  /v1/auth/login:
    post:
      summary: Log in
      security: []
      tags: [Auth]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email:
                  type: string
                password:
                  type: string
      responses:
        "201":
          description: Session token returned
        "401":
          description: Invalid credentials or unverified email

  /v1/auth/logout:
    post:
      summary: Revoke current session
      tags: [Auth]
      responses:
        "201":
          description: Session revoked

  /v1/auth/forgot-password:
    post:
      summary: Request password reset code
      security: []
      tags: [Auth]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email:
                  type: string
      responses:
        "201":
          description: Reset code sent (if email exists)

  /v1/auth/reset-password:
    post:
      summary: Reset password with code
      security: []
      tags: [Auth]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, code, new_password]
              properties:
                email:
                  type: string
                code:
                  type: string
                new_password:
                  type: string
                  minLength: 8
      responses:
        "201":
          description: Password updated

  /v1/auth/me:
    get:
      summary: Get current account info
      tags: [Auth]
      responses:
        "200":
          description: Account and user info

  /v1/api-keys:
    post:
      summary: Create API key
      tags: [API Keys]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                permissions:
                  type: array
                  items:
                    type: string
                    enum: [read, write, admin]
            example:
              name: Production Key
              permissions: [read, write]
      responses:
        "201":
          description: API key created (key shown once)
    get:
      summary: List API keys
      tags: [API Keys]
      responses:
        "200":
          description: List of API keys (without secrets)

  /v1/api-keys/{key_id}:
    delete:
      summary: Revoke API key
      tags: [API Keys]
      parameters:
        - name: key_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Key revoked

  /v1/inboxes:
    post:
      summary: Create inbox
      tags: [Inboxes]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                display_name:
                  type: string
                domain:
                  type: string
                  default: deadsimple.email
                local_part:
                  type: string
                tags:
                  type: array
                  items:
                    type: string
            example:
              display_name: Support Bot
              tags: [support, bot]
      responses:
        "201":
          description: Inbox created with email address
    get:
      summary: List inboxes
      tags: [Inboxes]
      parameters:
        - name: domain
          in: query
          schema:
            type: string
        - name: status
          in: query
          schema:
            type: string
            enum: [active, paused]
        - name: tag
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: cursor
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Paginated list of inboxes

  /v1/inboxes/bulk:
    post:
      summary: Bulk create inboxes (up to 100)
      tags: [Inboxes]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [inboxes]
              properties:
                inboxes:
                  type: array
                  maxItems: 100
                  items:
                    type: object
                    properties:
                      display_name:
                        type: string
                      tags:
                        type: array
                        items:
                          type: string
      responses:
        "201":
          description: Per-inbox creation results

  /v1/inboxes/{inbox_id}:
    get:
      summary: Get inbox
      tags: [Inboxes]
      parameters:
        - name: inbox_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Inbox details
    patch:
      summary: Update inbox
      tags: [Inboxes]
      parameters:
        - name: inbox_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                display_name:
                  type: string
                tags:
                  type: array
                  items:
                    type: string
                status:
                  type: string
                  enum: [active, paused]
      responses:
        "200":
          description: Updated inbox
    delete:
      summary: Delete inbox
      tags: [Inboxes]
      parameters:
        - name: inbox_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Inbox deleted and mailbox deprovisioned

  /v1/inboxes/{inbox_id}/messages:
    get:
      summary: List messages
      tags: [Messages]
      parameters:
        - name: inbox_id
          in: path
          required: true
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: cursor
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Paginated list of messages
    post:
      summary: Send email
      tags: [Messages]
      parameters:
        - name: inbox_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [to, subject]
              properties:
                to:
                  oneOf:
                    - type: string
                    - type: array
                      items:
                        type: string
                subject:
                  type: string
                text_body:
                  type: string
                html_body:
                  type: string
                cc:
                  type: array
                  items:
                    type: string
                bcc:
                  type: array
                  items:
                    type: string
                reply_to:
                  type: string
                headers:
                  type: object
                tracking:
                  type: boolean
                  default: false
                  description: Enable open/click tracking (HTML emails only)
                send_at:
                  type: string
                  format: date-time
                  description: Schedule send for a future time (ISO 8601)
                template_id:
                  type: string
                  format: uuid
                  description: Render a template instead of using body fields
                variables:
                  type: object
                  description: Template variable substitutions
                in_reply_to:
                  type: string
                  description: Message-ID header of the parent message
                references:
                  type: array
                  items:
                    type: string
                  description: Message-ID headers for threading
                attachments:
                  type: array
                  items:
                    type: object
                    properties:
                      filename:
                        type: string
                      content_type:
                        type: string
                      data:
                        type: string
                        description: Base64-encoded file content
            example:
              to: [user@example.com]
              subject: Hello from my AI agent
              text_body: This email was sent programmatically.
              tracking: false
      responses:
        "201":
          description: Email sent

  /v1/inboxes/{inbox_id}/messages/{message_id}:
    get:
      summary: Get message with full body
      tags: [Messages]
      parameters:
        - name: inbox_id
          in: path
          required: true
          schema:
            type: string
        - name: message_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Full message with body, headers, attachments

  /v1/inboxes/{inbox_id}/messages/{message_id}/reply:
    post:
      summary: Reply to a message
      tags: [Messages]
      parameters:
        - name: inbox_id
          in: path
          required: true
          schema:
            type: string
        - name: message_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                text_body:
                  type: string
                html_body:
                  type: string
      responses:
        "201":
          description: Reply sent with threading headers auto-set

  /v1/inboxes/{inbox_id}/messages/{message_id}/reply-all:
    post:
      summary: Reply-all to a message
      tags: [Messages]
      parameters:
        - name: inbox_id
          in: path
          required: true
          schema:
            type: string
        - name: message_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                text_body:
                  type: string
                html_body:
                  type: string
      responses:
        "201":
          description: Reply-all sent

  /v1/inboxes/{inbox_id}/messages/{message_id}/forward:
    post:
      summary: Forward a message
      tags: [Messages]
      parameters:
        - name: inbox_id
          in: path
          required: true
          schema:
            type: string
        - name: message_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [to]
              properties:
                to:
                  type: array
                  items:
                    type: string
                text_body:
                  type: string
                html_body:
                  type: string
      responses:
        "201":
          description: Message forwarded

  /v1/inboxes/{inbox_id}/messages/{message_id}/attachments/{attachment_id}:
    get:
      summary: Get attachment download URL
      tags: [Messages]
      parameters:
        - name: inbox_id
          in: path
          required: true
          schema:
            type: string
        - name: message_id
          in: path
          required: true
          schema:
            type: string
        - name: attachment_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Signed download URL (expires in 1 hour)

  /v1/inboxes/{inbox_id}/threads:
    get:
      summary: List threads
      tags: [Threads]
      parameters:
        - name: inbox_id
          in: path
          required: true
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
      responses:
        "200":
          description: Thread summaries with latest message

  /v1/inboxes/{inbox_id}/threads/{thread_id}:
    get:
      summary: Get thread with all messages
      tags: [Threads]
      parameters:
        - name: inbox_id
          in: path
          required: true
          schema:
            type: string
        - name: thread_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: All messages in the thread, chronological

  /v1/domains:
    post:
      summary: Add custom domain
      tags: [Domains]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [domain]
              properties:
                domain:
                  type: string
            example:
              domain: mail.yourcompany.com
      responses:
        "201":
          description: Domain added with DNS records to configure
    get:
      summary: List domains
      tags: [Domains]
      responses:
        "200":
          description: List of custom domains

  /v1/domains/{domain_id}/verify:
    get:
      summary: Verify domain DNS records
      tags: [Domains]
      parameters:
        - name: domain_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: DNS verification status

  /v1/domains/{domain_id}:
    delete:
      summary: Remove domain
      tags: [Domains]
      parameters:
        - name: domain_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Domain removed

  /v1/webhooks:
    post:
      summary: Register webhook
      tags: [Webhooks]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                    enum: [message.received, message.sent, message.delivered, message.bounced, message.complained, message.rejected, message.opened, message.link_clicked, inbox.created, inbox.deleted, domain.verified]
                description:
                  type: string
            example:
              url: https://your-app.com/webhooks/email
              events: [message.received]
              description: Notify on new inbound email
      responses:
        "201":
          description: Webhook created with signing secret (shown once)
    get:
      summary: List webhooks
      tags: [Webhooks]
      responses:
        "200":
          description: List of webhooks

  /v1/webhooks/{webhook_id}:
    delete:
      summary: Delete webhook
      tags: [Webhooks]
      parameters:
        - name: webhook_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Webhook deleted

  /v1/inboxes/{inbox_id}/messages/{message_id}/tracking:
    get:
      summary: Get open/click tracking stats
      tags: [Tracking]
      parameters:
        - name: inbox_id
          in: path
          required: true
          schema:
            type: string
        - name: message_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Tracking statistics
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/TrackingStats"

  /v1/t/o/{tracking_id}:
    get:
      summary: Tracking pixel (records email open)
      security: []
      tags: [Tracking]
      parameters:
        - name: tracking_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: 1x1 transparent GIF
          content:
            image/gif:
              schema:
                type: string
                format: binary

  /v1/t/c/{tracking_id}:
    get:
      summary: Click redirect (records link click)
      security: []
      tags: [Tracking]
      parameters:
        - name: tracking_id
          in: path
          required: true
          schema:
            type: string
        - name: url
          in: query
          required: true
          schema:
            type: string
            format: uri
          description: Original URL to redirect to
      responses:
        "302":
          description: Redirect to original URL

  /v1/usage:
    get:
      summary: Get usage metrics
      tags: [Usage]
      responses:
        "200":
          description: Current billing period usage stats
