openapi: 3.1.0
info:
  title: SuperSend TX API
  version: 0.9.0
  description: |
    Transactional email API for developers.
    Auth: `Authorization: Bearer stx_…` (or dashboard session cookie for integrations).
    Production base URL: https://api.supersendtx.com
tags:
  - name: ApiKeys
    description: Manage API keys (list, create, revoke)
  - name: Deliverability
    description: Best-effort metrics computed from stored email events
  - name: Domains
  - name: Inbound
    description: Receive domains, inbound ingestion, and received email retrieval
  - name: Emails
  - name: Events
    description: Custom events that trigger transactional automations
  - name: Automations
    description: Create and manage event-driven transactional automations
  - name: Integrations
  - name: Suppressions
    description: Account-level recipient suppressions (bounce/complaint/manual)
  - name: Templates
    description: Email templates with `{{variable}}` interpolation
  - name: Webhooks
servers:
  - url: https://api.supersendtx.com
    description: Production
paths:
  /integrations/cloudflare:
    get:
      operationId: getCloudflareIntegration
      summary: Get Cloudflare integration status
      tags: [Integrations]
      description: Session cookie auth (dashboard). Never returns the raw API token.
      responses:
        '200':
          description: Connection status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CloudflareIntegrationResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
    put:
      operationId: putCloudflareIntegration
      summary: Save Cloudflare API token
      tags: [Integrations]
      description: Session cookie auth. Token is encrypted at rest.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [apiToken]
              properties:
                apiToken:
                  type: string
                  description: Cloudflare API token with Zone DNS Edit
      responses:
        '200':
          description: Token saved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CloudflareIntegrationResponse'
        '400':
          description: Missing token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
        '503':
          description: Encryption key not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
    delete:
      operationId: deleteCloudflareIntegration
      summary: Disconnect Cloudflare
      tags: [Integrations]
      description: Session cookie auth. Clears the stored token.
      responses:
        '200':
          description: Disconnected
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CloudflareIntegrationResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
  /domains:
    get:
      operationId: listDomains
      summary: List sending domains
      tags: [Domains]
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          description: Maximum number of domains to return (1–100)
        - name: cursor
          in: query
          schema:
            type: string
          description: Opaque cursor for the next page
        - name: inbound_enabled
          in: query
          schema:
            type: boolean
          description: Filter by whether inbound receiving is enabled for the domain
      responses:
        '200':
          description: Domain list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListDomainsResponse'
        '401':
          description: Missing or invalid API key / session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
    post:
      operationId: createDomain
      summary: Add a sending domain
      tags: [Domains]
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateDomainRequest'
      responses:
        '200':
          description: Domain created with DNS records to add
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateDomainResponse'
        '400':
          description: Invalid domain
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
        '403':
          description: Plan domain limit reached (when billing is enabled)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  message: Domain limit reached (1 on the Free plan). Upgrade at /settings/billing.
                  code: domain_limit
                  upgrade_url: /settings/billing
        '409':
          description: Domain already added on this team, or already owned by another team
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  message: Domain already belongs to another SuperSend TX team. Remove it from the current team or contact support to claim it.
                  code: conflict
                  details:
                    reason: domain_claim_required
                    domain: example.com
                    instructions:
                      - Ask the current SuperSend TX team admin to remove the domain if you still have access.
                      - If you own the domain but no longer control the original team, contact support and include proof of DNS control.
  /domains/{id}:
    get:
      operationId: getDomain
      summary: Get a domain and DNS records
      tags: [Domains]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Domain id or domain name
          schema:
            type: string
      responses:
        '200':
          description: Domain detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DomainDetailResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
        '404':
          description: Domain not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
    post:
      operationId: domainAction
      summary: Verify domain DNS or apply DNS via Cloudflare or GoDaddy
      tags: [Domains]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Domain id or domain name
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [action]
              properties:
                action:
                  type: string
                  enum: [verify, apply]
                provider:
                  type: string
                  enum: [cloudflare, godaddy]
                  description: Required when action is apply
                credentials:
                  $ref: '#/components/schemas/RemoteApplyDnsCredentials'
      responses:
        '200':
          description: Domain verified, or DNS apply results
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/VerifyDomainResponse'
                  - $ref: '#/components/schemas/ApplyDomainDnsResponse'
        '400':
          description: DNS not ready, missing provider credentials, or invalid action
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/VerifyDomainErrorResponse'
                  - $ref: '#/components/schemas/SimpleErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
        '404':
          description: Domain not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
        '502':
          description: Domain registration failed, or DNS provider API error during apply
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/VerifyDomainErrorResponse'
                  - $ref: '#/components/schemas/SimpleErrorResponse'
        '503':
          description: Credentials encryption key not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
    patch:
      operationId: updateDomain
      summary: Update domain tracking and TLS preferences
      tags: [Domains]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Domain id or domain name
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateDomainRequest'
      responses:
        '200':
          description: Domain settings updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DomainDetailResponse'
        '400':
          description: Invalid JSON or settings payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
        '404':
          description: Domain not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
    delete:
      operationId: deleteDomain
      summary: Delete a domain
      description: |
        Removes the domain from the account and revokes its sending authorization.
        If mail-server teardown fails, the domain row is kept and the API returns 502.
      tags: [Domains]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Domain deleted
          content:
            application/json:
              schema:
                type: object
                required: [ok]
                properties:
                  ok:
                    type: boolean
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
        '404':
          description: Domain not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
        '502':
          description: Failed to revoke sending authorization upstream
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
  /deliverability:
    get:
      operationId: getDeliverability
      summary: Get deliverability insights for recent sends
      tags: [Deliverability]
      security:
        - bearerAuth: []
      parameters:
        - name: window
          in: query
          required: false
          schema:
            type: string
            enum: [7d, 30d]
            default: 30d
      responses:
        '200':
          description: Deliverability insights
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliverabilityInsightsResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /events:
    post:
      operationId: triggerEvent
      summary: Ingest a custom event for automations
      tags: [Events]
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            maxLength: 256
          description: Optional idempotency key for replay-safe retries.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TriggerEventRequest'
            examples:
              userCreated:
                value:
                  name: user.created
                  user_id: user_123
                  email: user@example.com
                  data:
                    name: Ada Lovelace
      responses:
        '202':
          description: Event accepted for automation processing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TriggerEventResponse'
        '400':
          description: Invalid event payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key / session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Idempotency conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /automations:
    get:
      operationId: listAutomations
      summary: List automations
      tags: [Automations]
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Automation list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListAutomationsResponse'
        '401':
          description: Missing or invalid API key / session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      operationId: createAutomation
      summary: Create a draft automation
      tags: [Automations]
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAutomationRequest'
            examples:
              welcome:
                value:
                  name: Welcome email
                  trigger:
                    type: event
                    event: user.created
                  steps:
                    - type: send_email
                      email:
                        from: you@yourdomain.com
                        to: '{{event.email}}'
                        subject: Welcome
                        html: '<p>Hi {{event.data.name}}</p>'
      responses:
        '201':
          description: Automation created as draft
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutomationResponse'
        '400':
          description: Invalid automation definition
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key / session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Automation name already in use
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /automations/{id}:
    get:
      operationId: getAutomation
      summary: Get an automation
      tags: [Automations]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Automation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutomationResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      operationId: updateAutomation
      summary: Update an automation definition
      tags: [Automations]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateAutomationRequest'
      responses:
        '200':
          description: Updated automation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutomationResponse'
        '400':
          description: Invalid update
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Name conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      operationId: deleteAutomation
      summary: Delete an automation
      tags: [Automations]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                type: object
                required: [ok]
                properties:
                  ok:
                    type: boolean
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      operationId: automationAction
      summary: Activate or pause an automation
      tags: [Automations]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AutomationActionRequest'
      responses:
        '200':
          description: Updated automation status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutomationResponse'
        '400':
          description: Invalid action
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /automations/runs:
    get:
      operationId: listAutomationRuns
      summary: List automation runs
      tags: [Automations]
      security:
        - bearerAuth: []
      parameters:
        - name: automation_id
          in: query
          required: false
          schema:
            type: string
          description: Filter runs to a single automation
      responses:
        '200':
          description: Run list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListAutomationRunsResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /automations/runs/{id}:
    get:
      operationId: getAutomationRun
      summary: Get an automation run
      tags: [Automations]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Run detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutomationRunResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      operationId: automationRunAction
      summary: Cancel or retry an automation run
      tags: [Automations]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AutomationRunActionRequest'
      responses:
        '200':
          description: Updated run
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutomationRunResponse'
        '400':
          description: Invalid action
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /emails:
    get:
      operationId: listEmails
      summary: List recent sent emails
      tags: [Emails]
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          description: Maximum number of emails to return (1–100)
        - name: cursor
          in: query
          schema:
            type: string
          description: Opaque cursor for the next page
      responses:
        '200':
          description: Email list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListEmailsResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      operationId: sendEmail
      summary: Send a transactional email
      tags: [Emails]
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            maxLength: 256
          description: |
            Optional idempotency key (max 256 chars). Retries with the same key
            and identical body return the original response. Same key with a
            different body, or a concurrent in-flight request, returns 409.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendEmailRequest'
            examples:
              basic:
                value:
                  from: you@yourdomain.com
                  to: user@example.com
                  subject: Hello from SuperSend TX
                  html: '<p>It works.</p>'
      responses:
        '200':
          description: Email accepted for delivery
          headers:
            ratelimit-limit:
              description: Request quota for the current window
              schema:
                type: integer
            ratelimit-remaining:
              description: Remaining requests in the current window
              schema:
                type: integer
            ratelimit-reset:
              description: Unix timestamp when the current window resets
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendEmailResponse'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: From domain not verified, or sandbox sender used for non-account recipients
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Idempotency key conflict (payload mismatch or concurrent request)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  message: Idempotency key was already used with a different request body.
                  code: idempotency_conflict
        '429':
          description: Free plan send limit exceeded (daily or monthly; paid plans meter overage instead)
          headers:
            ratelimit-limit:
              description: Request quota for the current window
              schema:
                type: integer
            ratelimit-remaining:
              description: Remaining requests in the current window
              schema:
                type: integer
            ratelimit-reset:
              description: Unix timestamp when the current window resets
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  message: Monthly send limit reached (3000/month on the free plan). Upgrade at /settings/billing.
                  code: plan_limit
                  upgrade_url: /settings/billing
        '502':
          description: Upstream mail server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Mail server not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /emails/batch:
    post:
      operationId: sendEmailBatch
      summary: Send a batch of transactional emails
      tags: [Emails]
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BatchSendEmailRequest'
      responses:
        '200':
          description: Index-aligned per-message results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchSendEmailResponse'
        '400':
          description: Invalid batch wrapper
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Mail server not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /emails/{id}:
    get:
      operationId: getEmail
      summary: Retrieve a sent email
      tags: [Emails]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Public message ID (`msg_…`)
          schema:
            type: string
      responses:
        '200':
          description: Email detail
          content:
            application/json:
              schema:
                type: object
                required: [email]
                properties:
                  email:
                    $ref: '#/components/schemas/EmailListItem'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Email not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      operationId: updateScheduledEmail
      summary: Reschedule or cancel a scheduled email
      tags: [Emails]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Public message ID (`msg_…`)
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateScheduledEmailRequest'
            examples:
              reschedule:
                value:
                  scheduled_at: '2026-08-01T12:00:00.000Z'
              cancel:
                value:
                  cancel: true
      responses:
        '200':
          description: Scheduled email updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendEmailResponse'
        '400':
          description: Email is not scheduled, or request is invalid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Email not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /emails/{id}/resend:
    post:
      operationId: resendEmail
      summary: Resend a previously stored email
      description: |
        Creates a **new** send using the stored from/to/subject/body (and tags/headers)
        from an existing message. Returns a new `msg_…` id. Attachment bytes are not
        stored, so attachments are omitted on resend.
      tags: [Emails]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Public message ID of the source email (`msg_…`)
          schema:
            type: string
      responses:
        '200':
          description: New email accepted for delivery
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendEmailResponse'
        '400':
          description: Source email has no stored body, or request is invalid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid credentials
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: From domain is not verified
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Source email not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Mail server not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /received-emails:
    get:
      operationId: listReceivedEmails
      summary: List received emails
      tags: [Inbound]
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: cursor
          in: query
          schema:
            type: string
        - name: domain
          in: query
          schema:
            type: string
          description: Filter by receiving domain name
      responses:
        '200':
          description: Received email list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListReceivedEmailsResponse'
        '401':
          description: Missing or invalid API key / session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /received-emails/{id}:
    get:
      operationId: getReceivedEmail
      summary: Retrieve a received email with attachment data
      tags: [Inbound]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Received email public id (`inb_…`)
          schema:
            type: string
      responses:
        '200':
          description: Received email detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReceivedEmailResponse'
        '401':
          description: Missing or invalid API key / session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Received email not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /webhooks:
    get:
      operationId: listWebhooks
      summary: List webhook endpoints
      tags: [Webhooks]
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          description: Maximum number of webhooks to return (1–100)
        - name: cursor
          in: query
          schema:
            type: string
          description: Opaque cursor for the next page
      responses:
        '200':
          description: Webhook endpoint list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListWebhooksResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      operationId: createWebhook
      summary: Create a webhook endpoint
      tags: [Webhooks]
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookRequest'
      responses:
        '200':
          description: Webhook created (signing secret shown once)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateWebhookResponse'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /webhooks/{id}:
    get:
      operationId: getWebhook
      summary: Retrieve a webhook endpoint
      tags: [Webhooks]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Webhook endpoint
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Webhook not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      operationId: updateWebhook
      summary: Update a webhook endpoint
      tags: [Webhooks]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateWebhookRequest'
      responses:
        '200':
          description: Webhook updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookResponse'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Webhook not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
    delete:
      operationId: deleteWebhook
      summary: Delete a webhook endpoint
      tags: [Webhooks]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Webhook deleted
          content:
            application/json:
              schema:
                type: object
                required: [ok]
                properties:
                  ok:
                    type: boolean
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Webhook not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleErrorResponse'
  /suppressions:
    get:
      operationId: listSuppressions
      summary: List suppressions
      tags: [Suppressions]
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: cursor
          in: query
          schema:
            type: string
        - name: email
          in: query
          schema:
            type: string
            format: email
          description: Filter by normalized email address
      responses:
        '200':
          description: Suppression list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListSuppressionsResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      operationId: createSuppression
      summary: Add an email to the suppression list
      tags: [Suppressions]
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSuppressionRequest'
      responses:
        '200':
          description: Suppression created or updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuppressionResponse'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      operationId: deleteSuppressionByEmail
      summary: Remove a suppression by email query parameter
      tags: [Suppressions]
      security:
        - bearerAuth: []
      parameters:
        - name: email
          in: query
          required: true
          schema:
            type: string
            format: email
      responses:
        '200':
          description: Suppression removed
          content:
            application/json:
              schema:
                type: object
                required: [ok]
                properties:
                  ok:
                    type: boolean
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Suppression not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /suppressions/{id}:
    delete:
      operationId: deleteSuppression
      summary: Remove a suppression by id
      tags: [Suppressions]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Suppression removed
          content:
            application/json:
              schema:
                type: object
                required: [ok]
                properties:
                  ok:
                    type: boolean
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Suppression not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /templates:
    get:
      operationId: listTemplates
      summary: List email templates
      tags: [Templates]
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: cursor
          in: query
          schema:
            type: string
        - name: status
          in: query
          schema:
            type: string
            enum: [draft, published]
          description: Filter by draft or published
      responses:
        '200':
          description: Template list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListTemplatesResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      operationId: createTemplate
      summary: Create an email template (draft)
      tags: [Templates]
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTemplateRequest'
      responses:
        '201':
          description: Template created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateResponse'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Name or alias conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /templates/{id}:
    get:
      operationId: getTemplate
      summary: Retrieve a template by id or alias
      tags: [Templates]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Template UUID or alias
          schema:
            type: string
      responses:
        '200':
          description: Template
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Template not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      operationId: updateTemplate
      summary: Update a template by id or alias
      tags: [Templates]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTemplateRequest'
      responses:
        '200':
          description: Template updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateResponse'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Template not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Name or alias conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      operationId: deleteTemplate
      summary: Delete a template by id or alias
      tags: [Templates]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Template deleted
          content:
            application/json:
              schema:
                type: object
                required: [ok]
                properties:
                  ok:
                    type: boolean
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Template not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      operationId: templateAction
      summary: Publish or duplicate a template
      tags: [Templates]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TemplateActionRequest'
      responses:
        '200':
          description: Publish succeeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateResponse'
        '201':
          description: Duplicate created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateResponse'
        '400':
          description: Invalid action
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Template not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /emails/test:
    post:
      operationId: testEmailWebhookEvent
      summary: Simulate a webhook event (test sink for CI)
      tags: [Emails]
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [event]
              properties:
                event:
                  $ref: '#/components/schemas/EmailWebhookEventType'
                email_id:
                  type: string
                  description: Optional existing email public id (`msg_…`)
                deliver:
                  type: boolean
                  default: true
                  description: When true, enqueue deliveries to account webhooks
      responses:
        '200':
          description: Payload built (and optionally delivered)
          content:
            application/json:
              schema:
                type: object
                required: [ok, payload, delivered]
                properties:
                  ok:
                    type: boolean
                  payload:
                    $ref: '#/components/schemas/EmailWebhookEventPayload'
                  delivered:
                    type: boolean
        '400':
          description: Invalid event
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api-keys:
    get:
      operationId: listApiKeys
      summary: List API keys
      tags: [ApiKeys]
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          description: Maximum number of API keys to return (1–100)
        - name: cursor
          in: query
          schema:
            type: string
          description: Opaque cursor for the next page
      responses:
        '200':
          description: API key list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListApiKeysResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      operationId: createApiKey
      summary: Create an API key
      tags: [ApiKeys]
      security:
        - bearerAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateApiKeyRequest'
      responses:
        '200':
          description: API key created (token shown once)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateApiKeyResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api-keys/{id}:
    delete:
      operationId: deleteApiKey
      summary: Revoke an API key
      tags: [ApiKeys]
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: API key revoked
          content:
            application/json:
              schema:
                type: object
                required: [ok]
                properties:
                  ok:
                    type: boolean
                    enum: [true]
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: API key not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: SuperSend TX API key prefixed with `stx_`
  schemas:
    SendEmailRequest:
      type: object
      required: [from, to]
      description: |
        `subject` is required unless `template` is provided (subject comes from the published template).
        Provide `html` and/or `text`, or `template` (mutually exclusive). React Email is compiled
        client-side by the Node SDK (`react` option) — do not send `react` to the HTTP API.
      properties:
        from:
          oneOf:
            - type: string
              format: email
            - type: object
              required: [email]
              properties:
                email:
                  type: string
                  format: email
                name:
                  type: string
        to:
          oneOf:
            - type: string
              format: email
            - type: array
              items:
                type: string
                format: email
        subject:
          type: string
          description: Required unless using `template` (optional override when sending with a template).
        html:
          type: string
        htmlBody:
          type: string
          description: Alias for html
        html_body:
          type: string
          description: Alias for html
        text:
          type: string
        textBody:
          type: string
          description: Alias for text
        text_body:
          type: string
          description: Alias for text
        plain_body:
          type: string
          description: Alias for text
        template:
          $ref: '#/components/schemas/SendTemplateRef'
        reply_to:
          oneOf:
            - type: string
              format: email
            - type: array
              items:
                type: string
                format: email
        replyTo:
          oneOf:
            - type: string
              format: email
            - type: array
              items:
                type: string
                format: email
          description: Alias for reply_to
        cc:
          oneOf:
            - type: string
            - type: array
              items:
                type: string
        bcc:
          oneOf:
            - type: string
            - type: array
              items:
                type: string
        tags:
          oneOf:
            - type: array
              maxItems: 10
              items:
                $ref: '#/components/schemas/EmailTag'
            - type: object
              additionalProperties:
                type: string
          description: Key/value tags stored with the email. SuperSend TX derives a single tag for delivery metadata.
        tag:
          type: string
          description: Optional delivery tag override.
        headers:
          type: object
          additionalProperties:
            type: string
          description: Custom headers after server-side safety filtering.
        attachments:
          type: array
          maxItems: 10
          description: Not supported for batch sends or scheduled sends in v1.
          items:
            $ref: '#/components/schemas/EmailAttachment'
        scheduled_at:
          type: string
          format: date-time
          description: Schedule delivery for a future ISO 8601 timestamp. Returns status `scheduled`.
        scheduledAt:
          type: string
          format: date-time
          description: Alias for scheduled_at.
    SendTemplateRef:
      type: object
      required: [id]
      description: Reference a published template by UUID or alias. Mutually exclusive with html/text.
      properties:
        id:
          type: string
          description: Template UUID or alias
        variables:
          type: object
          additionalProperties:
            oneOf:
              - type: string
              - type: number
              - type: boolean
          description: Values for `{{variable}}` placeholders (HTML-escaped in html body)
    EmailTag:
      type: object
      required: [name, value]
      properties:
        name:
          type: string
        value:
          type: string
    EmailAttachment:
      type: object
      required: [name, content_type, data]
      properties:
        name:
          type: string
          description: Filename sent to the recipient.
        filename:
          type: string
          description: Alias for name.
        content_type:
          type: string
          example: application/pdf
        contentType:
          type: string
          description: Alias for content_type.
        data:
          type: string
          format: byte
          description: Base64-encoded attachment content (10MB decoded max).
        content:
          type: string
          format: byte
          description: Alias for data (base64 string expected over HTTP).
        content_id:
          type: string
          description: Optional Content-ID for inline attachments.
        contentId:
          type: string
          description: Alias for content_id.
    BatchSendEmailRequest:
      type: object
      required: [emails]
      properties:
        emails:
          type: array
          maxItems: 100
          items:
            $ref: '#/components/schemas/SendEmailRequest'
          description: Attachments are rejected for batch sends in v1.
    BatchSendEmailResponse:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            type: object
            required: [index]
            properties:
              index:
                type: integer
              id:
                type: string
              status:
                type: string
              error:
                type: object
                properties:
                  message:
                    type: string
                  code:
                    type: string
                  status_code:
                    type: integer
                  details: {}
    UpdateScheduledEmailRequest:
      type: object
      properties:
        scheduled_at:
          type: string
          format: date-time
        scheduledAt:
          type: string
          format: date-time
          description: Alias for scheduled_at.
        cancel:
          type: boolean
    SendEmailResponse:
      type: object
      required: [id, status]
      properties:
        id:
          type: string
          description: Public message ID (`msg_…`)
        status:
          type: string
          example: sent
    EmailListItem:
      type: object
      required: [id, from, to, subject, status, created_at]
      properties:
        id:
          type: string
        from:
          type: string
        to:
          type: array
          items:
            type: string
            format: email
        cc:
          type: array
          items:
            type: string
            format: email
        bcc:
          type: array
          items:
            type: string
            format: email
        reply_to:
          type: array
          items:
            type: string
            format: email
        subject:
          type: string
        status:
          type: string
          enum: [queued, scheduled, sent, delivered, bounced, failed, cancelled]
        last_event:
          type: string
          nullable: true
        bounce_reason:
          type: string
          nullable: true
        tags:
          oneOf:
            - type: array
              items:
                $ref: '#/components/schemas/EmailTag'
            - type: object
          nullable: true
        scheduled_at:
          type: string
          format: date-time
          nullable: true
        cancelled_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        sent_at:
          type: string
          format: date-time
          nullable: true
        delivered_at:
          type: string
          format: date-time
          nullable: true
        bounced_at:
          type: string
          format: date-time
          nullable: true
    ListEmailsResponse:
      type: object
      required: [emails, has_more, next_cursor]
      properties:
        emails:
          type: array
          items:
            $ref: '#/components/schemas/EmailListItem'
        has_more:
          type: boolean
        next_cursor:
          type: string
          nullable: true
    ReceivedEmailAttachmentMeta:
      type: object
      required: [filename, content_type, size]
      properties:
        filename:
          type: string
        content_type:
          type: string
        size:
          type: integer
        content_id:
          type: string
          nullable: true
    ReceivedEmailAttachment:
      allOf:
        - $ref: '#/components/schemas/ReceivedEmailAttachmentMeta'
        - type: object
          properties:
            data:
              type: string
              format: byte
              description: Base64-encoded attachment content. Present on retrieve responses, omitted from list responses.
    ReceivedEmail:
      type: object
      required:
        [id, domain_id, domain_name, status, rcpt_to, mail_from, from, to, cc, subject, spam_status, bounced, received_with_ssl, message_id, in_reply_to, references, attachment_count, attachments, text_body, html_body, auto_submitted, received_at, created_at]
      properties:
        id:
          type: string
          description: Received email public id (`inb_…`)
        domain_id:
          type: string
        domain_name:
          type: string
        status:
          type: string
          enum: [received]
        rcpt_to:
          type: string
          format: email
          description: Envelope recipient as received by SuperSend TX.
        mail_from:
          type: string
          nullable: true
          description: SMTP envelope sender.
        from:
          type: string
        to:
          type: array
          items:
            type: string
            format: email
        cc:
          type: array
          items:
            type: string
            format: email
        subject:
          type: string
        spam_status:
          type: string
          nullable: true
        bounced:
          type: boolean
        received_with_ssl:
          type: boolean
          nullable: true
        message_id:
          type: string
          nullable: true
        in_reply_to:
          type: string
          nullable: true
        references:
          type: array
          items:
            type: string
        attachment_count:
          type: integer
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/ReceivedEmailAttachment'
        text_body:
          type: string
          nullable: true
        html_body:
          type: string
          nullable: true
        auto_submitted:
          type: string
          nullable: true
        received_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
    ListReceivedEmailsResponse:
      type: object
      required: [received_emails, has_more, next_cursor]
      properties:
        received_emails:
          type: array
          items:
            $ref: '#/components/schemas/ReceivedEmail'
        has_more:
          type: boolean
        next_cursor:
          type: string
          nullable: true
    ReceivedEmailResponse:
      type: object
      required: [received_email]
      properties:
        received_email:
          $ref: '#/components/schemas/ReceivedEmail'
    DeliverabilityBucket:
      type: object
      required:
        - total_emails
        - attempted
        - sent
        - delivered
        - bounced
        - failed
        - suppressed
        - scheduled
        - opened
        - clicked
        - bounce_rate
        - delivery_rate
        - open_rate
        - click_rate
      properties:
        total_emails:
          type: integer
        attempted:
          type: integer
        sent:
          type: integer
        delivered:
          type: integer
        bounced:
          type: integer
        failed:
          type: integer
        suppressed:
          type: integer
        scheduled:
          type: integer
        opened:
          type: integer
        clicked:
          type: integer
        bounce_rate:
          type: number
        delivery_rate:
          type: number
        open_rate:
          type: number
        click_rate:
          type: number
    DeliverabilityDomainInsight:
      allOf:
        - $ref: '#/components/schemas/DeliverabilityBucket'
        - type: object
          required: [domain]
          properties:
            domain:
              type: string
    DeliverabilityInsightsResponse:
      type: object
      required:
        - window
        - window_start
        - generated_at
        - summary
        - by_domain
        - top_bounce_reasons
        - notes
      properties:
        window:
          type: string
          enum: [7d, 30d]
        window_start:
          type: string
          format: date-time
        generated_at:
          type: string
          format: date-time
        summary:
          $ref: '#/components/schemas/DeliverabilityBucket'
        by_domain:
          type: array
          items:
            $ref: '#/components/schemas/DeliverabilityDomainInsight'
        top_bounce_reasons:
          type: array
          items:
            type: object
            required: [reason, count]
            properties:
              reason:
                type: string
              count:
                type: integer
        notes:
          type: array
          items:
            type: string
    PaginationMeta:
      type: object
      required: [has_more, next_cursor]
      properties:
        has_more:
          type: boolean
          description: Whether another page of results is available
        next_cursor:
          type: string
          nullable: true
          description: Cursor to pass as `cursor` for the next page, or null when has_more is false
    EmailWebhookEventType:
      type: string
      enum:
        - email.received
        - email.sent
        - email.delivered
        - email.delivery_delayed
        - email.bounced
        - email.complained
        - email.opened
        - email.clicked
        - email.failed
        - email.suppressed
        - email.scheduled
    WebhookEventType:
      type: string
      enum:
        - email.received
        - email.sent
        - email.delivered
        - email.delivery_delayed
        - email.bounced
        - email.complained
        - email.opened
        - email.clicked
        - email.failed
        - email.suppressed
        - email.scheduled
        - automation.started
        - automation.step_completed
        - automation.completed
        - automation.failed
    Suppression:
      type: object
      required: [id, email, reason, source, created_at, updated_at]
      properties:
        id:
          type: string
        email:
          type: string
          format: email
        reason:
          type: string
          nullable: true
        source:
          type: string
          enum: [bounce, complaint, manual, api]
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ListSuppressionsResponse:
      type: object
      required: [data, has_more, next_cursor]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Suppression'
        has_more:
          type: boolean
        next_cursor:
          type: string
          nullable: true
    CreateSuppressionRequest:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email
        reason:
          type: string
    SuppressionResponse:
      type: object
      required: [suppression]
      properties:
        suppression:
          $ref: '#/components/schemas/Suppression'
    Template:
      type: object
      required:
        - id
        - name
        - alias
        - status
        - subject
        - html
        - text
        - variables
        - published_at
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        alias:
          type: string
          nullable: true
          description: Optional unique alias per account (must not start with `stx_`)
        status:
          type: string
          enum: [draft, published]
        subject:
          type: string
        html:
          type: string
          nullable: true
        text:
          type: string
          nullable: true
        variables:
          type: array
          nullable: true
          items:
            type: string
          description: Schema hint — variable names used in subject/html/text
        published_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        warnings:
          type: array
          description: Non-blocking deliverability hints for template HTML (e.g. SVG image URLs)
          items:
            $ref: '#/components/schemas/TemplateLintWarning'
    TemplateLintWarning:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
          enum: [svg_image, relative_image_url, data_uri_image]
        message:
          type: string
        detail:
          type: string
          nullable: true
    TemplateResponse:
      type: object
      required: [template]
      properties:
        template:
          $ref: '#/components/schemas/Template'
    ListTemplatesResponse:
      type: object
      required: [data, has_more, next_cursor]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Template'
        has_more:
          type: boolean
        next_cursor:
          type: string
          nullable: true
    CreateTemplateRequest:
      type: object
      required: [name, subject]
      properties:
        name:
          type: string
        subject:
          type: string
        html:
          type: string
        text:
          type: string
        alias:
          type: string
        variables:
          type: array
          items:
            type: string
    UpdateTemplateRequest:
      type: object
      properties:
        name:
          type: string
        subject:
          type: string
        html:
          type: string
          nullable: true
        text:
          type: string
          nullable: true
        alias:
          type: string
          nullable: true
        variables:
          type: array
          nullable: true
          items:
            type: string
    TemplateActionRequest:
      type: object
      required: [action]
      properties:
        action:
          type: string
          enum: [publish, duplicate]
        name:
          type: string
          description: Optional name when action is `duplicate`
    WebhookEndpoint:
      type: object
      required: [id, url, events, enabled, created_at, updated_at]
      properties:
        id:
          type: string
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEventType'
        enabled:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ListWebhooksResponse:
      type: object
      required: [webhooks, has_more, next_cursor]
      properties:
        webhooks:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEndpoint'
        has_more:
          type: boolean
        next_cursor:
          type: string
          nullable: true
    CreateWebhookRequest:
      type: object
      required: [url]
      properties:
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEventType'
          description: Defaults to all supported events if omitted
    CreateWebhookResponse:
      type: object
      required: [webhook, secret]
      properties:
        webhook:
          $ref: '#/components/schemas/WebhookEndpoint'
        secret:
          type: string
          description: Signing secret (`whsec_…`), shown once at creation
    UpdateWebhookRequest:
      type: object
      properties:
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEventType'
        enabled:
          type: boolean
    WebhookResponse:
      type: object
      required: [webhook]
      properties:
        webhook:
          $ref: '#/components/schemas/WebhookEndpoint'
    EmailWebhookEventPayload:
      type: object
      required: [type, created_at, data]
      properties:
        type:
          $ref: '#/components/schemas/EmailWebhookEventType'
        created_at:
          type: string
          format: date-time
        data:
          type: object
          required: [email_id, to, from, subject, status]
          properties:
            email_id:
              type: string
            to:
              type: array
              items:
                type: string
                format: email
            from:
              type: string
            subject:
              type: string
            status:
              type: string
            bounce_reason:
              type: string
              nullable: true
            rcpt_to:
              type: string
              format: email
            cc:
              type: array
              items:
                type: string
                format: email
            message_id:
              type: string
              nullable: true
            in_reply_to:
              type: string
              nullable: true
            references:
              type: array
              items:
                type: string
            spam_status:
              type: string
              nullable: true
            attachment_count:
              type: integer
            attachments:
              type: array
              items:
                $ref: '#/components/schemas/ReceivedEmailAttachmentMeta'
            received_at:
              type: string
              format: date-time
    AutomationWebhookEventPayload:
      type: object
      required: [type, created_at, data]
      properties:
        type:
          type: string
          enum:
            - automation.started
            - automation.step_completed
            - automation.completed
            - automation.failed
        created_at:
          type: string
          format: date-time
        data:
          type: object
          required: [automation_id, automation_name, run_id, status, event_name]
          properties:
            automation_id:
              type: string
            automation_name:
              type: string
            run_id:
              type: string
            status:
              type: string
            event_name:
              type: string
            step_index:
              type: integer
              nullable: true
            step_type:
              type: string
              nullable: true
              enum: [delay, send_email, condition, wait_for_event]
            error:
              type: string
              nullable: true
    WebhookEventPayload:
      oneOf:
        - $ref: '#/components/schemas/EmailWebhookEventPayload'
        - $ref: '#/components/schemas/AutomationWebhookEventPayload'
    EventRecord:
      type: object
      required: [id, name, user_id, email, data, source, created_at]
      properties:
        id:
          type: string
        name:
          type: string
        user_id:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
          format: email
        data:
          type: object
          additionalProperties: true
        source:
          type: string
          enum: [api, dashboard]
        created_at:
          type: string
          format: date-time
    TriggerEventRequest:
      type: object
      properties:
        name:
          type: string
          description: "Canonical event name (alias: `type`)"
        type:
          type: string
          description: Alias for name
        user_id:
          type: string
        userId:
          type: string
          description: Alias for user_id
        email:
          type: string
          format: email
        data:
          type: object
          additionalProperties: true
      anyOf:
        - required: [name]
        - required: [type]
    TriggerEventResponse:
      type: object
      required: [event, matched_automations, resumed_runs]
      properties:
        event:
          $ref: '#/components/schemas/EventRecord'
        matched_automations:
          type: integer
        resumed_runs:
          type: integer
    AutomationTrigger:
      type: object
      required: [type, event]
      properties:
        type:
          type: string
          enum: [event]
        event:
          type: string
          example: user.created
    AutomationStep:
      type: object
      required: [type]
      properties:
        type:
          type: string
          enum: [delay, send_email, condition, wait_for_event]
      additionalProperties: true
      description: |
        Step object. Shape depends on `type`:
        - `delay`: `{ type, seconds }`
        - `send_email`: `{ type, email: { from, to, subject?, html?, text?, template? } }`
        - `condition`: `{ type, path, operator, value? }` (false stops the run)
        - `wait_for_event`: `{ type, event, timeout_seconds? }`
    Automation:
      type: object
      required: [id, name, status, trigger, steps, created_at, updated_at]
      properties:
        id:
          type: string
        name:
          type: string
        status:
          type: string
          enum: [draft, active, paused]
        trigger:
          $ref: '#/components/schemas/AutomationTrigger'
        steps:
          type: array
          items:
            $ref: '#/components/schemas/AutomationStep'
          minItems: 1
          maxItems: 20
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ListAutomationsResponse:
      type: object
      required: [automations]
      properties:
        automations:
          type: array
          items:
            $ref: '#/components/schemas/Automation'
    AutomationResponse:
      type: object
      required: [automation]
      properties:
        automation:
          $ref: '#/components/schemas/Automation'
    CreateAutomationRequest:
      type: object
      required: [name, trigger, steps]
      properties:
        name:
          type: string
        trigger:
          $ref: '#/components/schemas/AutomationTrigger'
        steps:
          type: array
          items:
            $ref: '#/components/schemas/AutomationStep'
          minItems: 1
          maxItems: 20
    UpdateAutomationRequest:
      type: object
      properties:
        name:
          type: string
        trigger:
          $ref: '#/components/schemas/AutomationTrigger'
        steps:
          type: array
          items:
            $ref: '#/components/schemas/AutomationStep'
          minItems: 1
          maxItems: 20
    AutomationActionRequest:
      type: object
      required: [action]
      properties:
        action:
          type: string
          enum: [activate, pause]
    AutomationRunStep:
      type: object
      required:
        [id, step_index, step_type, status, output, error, started_at, completed_at, created_at, updated_at]
      properties:
        id:
          type: string
        step_index:
          type: integer
        step_type:
          type: string
          enum: [delay, send_email, condition, wait_for_event]
        status:
          type: string
          enum: [started, waiting, completed, failed]
        output:
          nullable: true
        error:
          type: string
          nullable: true
        started_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    AutomationRun:
      type: object
      required:
        [
          id,
          automation_id,
          trigger_event_id,
          status,
          current_step,
          waiting_for_event,
          wait_timeout_at,
          context,
          last_error,
          cancel_requested_at,
          started_at,
          completed_at,
          created_at,
          updated_at,
        ]
      properties:
        id:
          type: string
        automation_id:
          type: string
        trigger_event_id:
          type: string
          nullable: true
        status:
          type: string
          enum: [queued, running, waiting, completed, failed, cancelled]
        current_step:
          type: integer
        waiting_for_event:
          type: string
          nullable: true
        wait_timeout_at:
          type: string
          format: date-time
          nullable: true
        context:
          type: object
          additionalProperties: true
        last_error:
          type: string
          nullable: true
        cancel_requested_at:
          type: string
          format: date-time
          nullable: true
        started_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        automation:
          $ref: '#/components/schemas/Automation'
        trigger_event:
          $ref: '#/components/schemas/EventRecord'
        steps:
          type: array
          items:
            $ref: '#/components/schemas/AutomationRunStep'
          description: Present on run detail responses
    ListAutomationRunsResponse:
      type: object
      required: [runs]
      properties:
        runs:
          type: array
          items:
            $ref: '#/components/schemas/AutomationRun'
    AutomationRunResponse:
      type: object
      required: [run]
      properties:
        run:
          $ref: '#/components/schemas/AutomationRun'
    AutomationRunActionRequest:
      type: object
      required: [action]
      properties:
        action:
          type: string
          enum: [cancel, retry]
    Domain:
      type: object
      required:
        [id, name, status, created_at, verified_at, open_tracking, click_tracking, tls_mode, inbound_enabled, inbound_status, inbound_error]
      properties:
        id:
          type: string
        name:
          type: string
          example: example.com
        status:
          type: string
          enum: [pending, verified, failed]
        created_at:
          type: string
          format: date-time
        verified_at:
          type: string
          format: date-time
          nullable: true
        open_tracking:
          type: boolean
          description: Open tracking preference for this domain.
        click_tracking:
          type: boolean
          description: Click tracking preference for this domain.
        tls_mode:
          type: string
          enum: [opportunistic, enforced]
          description: Preferred outbound TLS behavior for this domain.
        inbound_enabled:
          type: boolean
          description: True when SuperSend TX should accept inbound mail for this domain.
        inbound_status:
          type: string
          enum: [disabled, pending, active, error]
          description: '`pending` before verification, `active` once inbound receiving is configured, or `error` when setup fails.'
        inbound_error:
          type: string
          nullable: true
          description: Last inbound configuration error, if any.
    DnsRecord:
      type: object
      required: [type, host, value, purpose]
      properties:
        type:
          type: string
          example: TXT
          description: DNS record type (`TXT`, `CNAME`, or `MX`)
        host:
          type: string
        value:
          type: string
        purpose:
          type: string
          description: Human-readable purpose (`Domain ownership`, `DKIM`, `Return path`, `SPF`, `DMARC`, `Inbound receiving`)
    ListDomainsResponse:
      type: object
      required: [domains, has_more, next_cursor]
      properties:
        domains:
          type: array
          items:
            $ref: '#/components/schemas/Domain'
        has_more:
          type: boolean
        next_cursor:
          type: string
          nullable: true
    CreateDomainRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
          example: example.com
        inbound_enabled:
          type: boolean
          description: Request inbound receiving for the domain as soon as verification succeeds.
        inboundEnabled:
          type: boolean
          description: Alias for inbound_enabled.
    CreateDomainResponse:
      type: object
      required: [domain, records, inbound_records, inbound_note]
      properties:
        domain:
          $ref: '#/components/schemas/Domain'
        records:
          type: array
          items:
            $ref: '#/components/schemas/DnsRecord'
        inbound_records:
          type: array
          items:
            $ref: '#/components/schemas/DnsRecord'
        inbound_note:
          type: string
        postal_dns_synced:
          type: boolean
          description: Whether Postal DKIM/return-path records were synced into the response records
        postal_dns_error:
          type: string
          description: Present when Postal DNS sync failed; ownership/SPF/DMARC records are still returned
    DomainDetailResponse:
      type: object
      required: [domain, records, analysis, inbound_records, inbound_note]
      properties:
        domain:
          $ref: '#/components/schemas/Domain'
        records:
          type: array
          items:
            $ref: '#/components/schemas/DnsRecord'
        analysis:
          $ref: '#/components/schemas/DomainDnsAnalysis'
        inbound_records:
          type: array
          items:
            $ref: '#/components/schemas/DnsRecord'
        inbound_note:
          type: string
        inbound_configured:
          type: boolean
        inbound_pending:
          type: boolean
        inbound_error:
          type: string
          nullable: true
        tracking_dns:
          $ref: '#/components/schemas/DomainTrackingDns'
        tracking_postal_error:
          type: string
          nullable: true
          description: Present when tracking preferences were saved but Postal track-domain provisioning failed.
    DomainTrackingDns:
      type: object
      required: [required, host, target, ok]
      properties:
        required:
          type: boolean
          description: True when open or click tracking is enabled for this domain.
        host:
          type: string
          nullable: true
          description: Customer CNAME host (e.g. ps.example.com) when tracking is required.
        target:
          type: string
          nullable: true
          description: Postal tracking CNAME target when tracking is required.
        ok:
          type: boolean
          nullable: true
          description: Whether tracking DNS is live; null when tracking is disabled.
    VerifyDomainResponse:
      type: object
      required: [domain, records, verified, inbound_records, inbound_note]
      properties:
        domain:
          $ref: '#/components/schemas/Domain'
        records:
          type: array
          items:
            $ref: '#/components/schemas/DnsRecord'
        analysis:
          $ref: '#/components/schemas/DomainDnsAnalysis'
        inbound_records:
          type: array
          items:
            $ref: '#/components/schemas/DnsRecord'
        inbound_note:
          type: string
        verified:
          type: boolean
        auto:
          type: boolean
        checks:
          type: object
          properties:
            ownership:
              type: boolean
            spf:
              type: boolean
            dkim:
              type: boolean
            dmarc:
              type: boolean
            return_path:
              type: boolean
        inbound_configured:
          type: boolean
        inbound_pending:
          type: boolean
        inbound_error:
          type: string
          nullable: true
    VerifyDomainErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: string
        checks:
          type: object
          properties:
            ownership:
              type: boolean
            spf:
              type: boolean
            dkim:
              type: boolean
            dmarc:
              type: boolean
            return_path:
              type: boolean
        records:
          type: array
          items:
            $ref: '#/components/schemas/DnsRecord'
    RemoteApplyDnsCredentials:
      type: object
      properties:
        apiToken:
          type: string
          description: Inline Cloudflare API token instead of the stored dashboard integration
        zoneId:
          type: string
          description: Optional Cloudflare zone id when using an inline token
        cloudflareApiToken:
          type: string
          description: Alias for apiToken
        cloudflareZoneId:
          type: string
          description: Alias for zoneId
        apiKey:
          type: string
          description: GoDaddy API key
        apiSecret:
          type: string
          description: GoDaddy API secret
        godaddyApiKey:
          type: string
          description: Alias for apiKey
        godaddyApiSecret:
          type: string
          description: Alias for apiSecret
    ApplyDomainDnsResponse:
      type: object
      required: [provider, domain, results]
      properties:
        provider:
          type: string
          enum: [cloudflare, godaddy]
        domain:
          type: string
        results:
          type: array
          items:
            $ref: '#/components/schemas/ApplyDnsRecordResult'
    ApplyDnsRecordResult:
      type: object
      required: [purpose, host, action]
      properties:
        purpose:
          type: string
        host:
          type: string
        action:
          type: string
          enum: [created, updated, unchanged, merged]
        detail:
          type: string
    UpdateDomainRequest:
      type: object
      description: |
        Tracking and TLS settings are persisted immediately in SuperSend TX.
        Preferences are stored on the domain immediately when updated.
      properties:
        open_tracking:
          type: boolean
        openTracking:
          type: boolean
          description: Alias for open_tracking
        click_tracking:
          type: boolean
        clickTracking:
          type: boolean
          description: Alias for click_tracking
        tls_mode:
          type: string
          enum: [opportunistic, enforced]
        tlsMode:
          type: string
          enum: [opportunistic, enforced]
          description: Alias for tls_mode
        inbound_enabled:
          type: boolean
          description: Enable or disable inbound receiving for this domain.
        inboundEnabled:
          type: boolean
          description: Alias for inbound_enabled
    DomainDnsAnalysis:
      type: object
      required: [dmarc, bimi]
      properties:
        dmarc:
          $ref: '#/components/schemas/DomainDmarcAnalysis'
        bimi:
          $ref: '#/components/schemas/DomainBimiAnalysis'
    DomainDmarcAnalysis:
      type: object
      required: [host, configured, record, policy, rua, guidance]
      properties:
        host:
          type: string
        configured:
          type: boolean
        record:
          type: string
          nullable: true
        policy:
          type: string
          nullable: true
          description: Parsed DMARC `p=` policy when present (`none`, `quarantine`, or `reject`)
        rua:
          type: array
          items:
            type: string
        guidance:
          type: string
    DomainBimiAnalysis:
      type: object
      required:
        [host, configured, record, location, authority, requires_dmarc_enforcement, guidance]
      properties:
        host:
          type: string
        configured:
          type: boolean
        record:
          type: string
          nullable: true
        location:
          type: string
          nullable: true
          description: Parsed BIMI `l=` logo URL when present
        authority:
          type: string
          nullable: true
          description: Parsed BIMI `a=` VMC URL when present
        requires_dmarc_enforcement:
          type: boolean
          description: True when BIMI is unlikely to work until DMARC is set to p=quarantine or p=reject
        guidance:
          type: string
    CloudflareIntegrationStatus:
      type: object
      required: [connected, hint, connectedAt]
      properties:
        connected:
          type: boolean
        hint:
          type: string
          nullable: true
          description: Last 4 characters of the stored token
        connectedAt:
          type: string
          format: date-time
          nullable: true
    CloudflareIntegrationResponse:
      type: object
      required: [cloudflare]
      properties:
        cloudflare:
          $ref: '#/components/schemas/CloudflareIntegrationStatus'
    ErrorCode:
      type: string
      description: Machine-readable error code
      enum:
        - validation_error
        - unauthorized
        - forbidden
        - not_found
        - conflict
        - plan_limit
        - domain_limit
        - idempotency_conflict
        - rate_limited
        - upstream_error
        - not_configured
        - internal_error
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [message]
          properties:
            message:
              type: string
            code:
              $ref: '#/components/schemas/ErrorCode'
            upgrade_url:
              type: string
              description: Dashboard path to upgrade when code is plan_limit or domain_limit
            details:
              description: Optional upstream or validation details
    ApiKey:
      type: object
      required: [id, name, scope, prefix, created_at]
      properties:
        id:
          type: string
        name:
          type: string
        scope:
          type: string
          enum: [full, sending]
        prefix:
          type: string
          description: Visible key prefix (e.g. `stx_live_ab`)
        created_at:
          type: string
          format: date-time
        last_used_at:
          type: string
          format: date-time
          nullable: true
    ListApiKeysResponse:
      type: object
      required: [data, has_more, next_cursor]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ApiKey'
        has_more:
          type: boolean
        next_cursor:
          type: string
          nullable: true
    CreateApiKeyRequest:
      type: object
      properties:
        name:
          type: string
        scope:
          type: string
          enum: [full, sending]
          default: full
    CreateApiKeyResponse:
      type: object
      required: [id, name, scope, prefix, token, created_at]
      properties:
        id:
          type: string
        name:
          type: string
        scope:
          type: string
          enum: [full, sending]
        prefix:
          type: string
        token:
          type: string
          description: Full API key (`stx_…`), shown once at creation
        created_at:
          type: string
          format: date-time
    SimpleErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: string
