openapi: 3.1.0
info:
  title: Sntid Email API
  version: '1.0'
  description: Queue email, read delivery history, and manage scheduled sends. The local provider simulates delivery.
servers:
  - url: /api/v1
security:
  - ApiKey: []
paths:
  /status:
    get:
      operationId: getPlatformStatus
      security: []
      responses:
        '200': { description: Provider, sandbox, region, and sending availability. }
  /domains:
    get:
      operationId: listSenderDomains
      description: Requires domain:manage. Lists the sender domains available to the authenticated workspace.
      responses:
        '200': { description: List of authorized domains and provider status. }
        default: { $ref: '#/components/responses/Error' }
  /emails:
    post:
      operationId: sendEmail
      description: Requires email:send. Acknowledges a queue entry. Use email details to check acceptance and delivery.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EmailInput' }
      responses:
        '202': { $ref: '#/components/responses/EmailReceipt' }
        default: { $ref: '#/components/responses/Error' }
    get:
      operationId: listEmails
      description: Requires email:read.
      parameters:
        - { name: q, in: query, schema: { type: string } }
        - { name: state, in: query, schema: { type: string } }
        - { name: cursor, in: query, schema: { type: string, format: uuid } }
      responses:
        '200':
          description: Page of email summaries.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Email' }
                  nextCursor: { type: [string, 'null'], format: uuid }
        default: { $ref: '#/components/responses/Error' }
  /emails/batch:
    post:
      operationId: sendEmailBatch
      description: Requires email:send. Queues a batch atomically.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [emails]
              properties:
                emails:
                  type: array
                  minItems: 1
                  maxItems: 50
                  items: { $ref: '#/components/schemas/EmailInput' }
      responses:
        '202':
          description: Accepted batch with one receipt per queued email.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/Receipt' }
        default: { $ref: '#/components/responses/Error' }
  /emails/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
    get:
      operationId: getEmail
      description: Requires email:read. Details include recipient states, the event timeline, and temporary attachment links.
      responses:
        '200':
          description: Email detail.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Email' }
        default: { $ref: '#/components/responses/Error' }
    patch:
      operationId: updateScheduledEmail
      description: Requires email:send. Reschedule, edit content, or replace the payload before sending starts. Use a null scheduleAt to send now.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/EmailInput'
                - type: object
                  additionalProperties: false
                  required: [scheduleAt]
                  properties:
                    scheduleAt: { type: [string, 'null'], format: date-time }
                - type: object
                  additionalProperties: false
                  required: [content]
                  properties:
                    content:
                      type: object
                      additionalProperties: false
                      required: [subject]
                      anyOf:
                        - required: [html]
                        - required: [text]
                      properties:
                        subject: { type: string, maxLength: 998 }
                        html: { type: string, maxLength: 500000 }
                        text: { type: string, maxLength: 500000 }
      responses:
        '200': { $ref: '#/components/responses/EmailReceipt' }
        default: { $ref: '#/components/responses/Error' }
    delete:
      operationId: cancelScheduledEmail
      description: Requires email:send. Cancel before sending starts.
      responses:
        '200': { $ref: '#/components/responses/EmailReceipt' }
        default: { $ref: '#/components/responses/Error' }
  /emails/{id}/attachments/{index}:
    get:
      operationId: downloadEmailAttachment
      description: Temporary capability URL returned by the email detail endpoint. Treat it like a credential.
      security: []
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
        - { name: index, in: path, required: true, schema: { type: integer, minimum: 0 } }
        - { name: token, in: query, required: true, schema: { type: string } }
      responses:
        '200': { description: Attachment served as a download. }
        default: { $ref: '#/components/responses/Error' }
  /webhooks:
    get:
      operationId: listWebhooks
      description: Requires webhook:manage.
      responses:
        '200': { description: Webhook subscriptions. }
        default: { $ref: '#/components/responses/Error' }
    post:
      operationId: createWebhook
      description: Requires webhook:manage. The response includes the signing secret once.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WebhookSettings'
                - required: [url, events]
      responses:
        '201': { description: New webhook subscription and secret. }
        default: { $ref: '#/components/responses/Error' }
  /webhooks/{id}:
    parameters:
      - { $ref: '#/components/parameters/WebhookId' }
    patch:
      operationId: updateWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WebhookSettings' }
      responses:
        '200': { description: Updated webhook subscription. }
        default: { $ref: '#/components/responses/Error' }
    delete:
      operationId: deleteWebhook
      responses:
        '204': { description: Deleted webhook subscription. }
        default: { $ref: '#/components/responses/Error' }
  /webhooks/{id}/attempts:
    get:
      operationId: listWebhookAttempts
      parameters:
        - { $ref: '#/components/parameters/WebhookId' }
      responses:
        '200': { description: Recent attempts with event IDs and delivery status. }
        default: { $ref: '#/components/responses/Error' }
  /webhooks/{id}/replay:
    post:
      operationId: replayWebhookEvent
      parameters:
        - { $ref: '#/components/parameters/WebhookId' }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [eventId]
              properties:
                eventId: { type: string, format: uuid }
      responses:
        '202': { description: Replay queued. }
        default: { $ref: '#/components/responses/Error' }
components:
  securitySchemes:
    ApiKey: { type: http, scheme: bearer }
  parameters:
    WebhookId:
      name: id
      in: path
      required: true
      schema: { type: string, format: uuid }
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      description: Replay the same request and receive the original receipt. A different payload using the same key in the same workspace and operation returns 409.
      schema: { type: string, minLength: 1, maxLength: 128 }
  responses:
    EmailReceipt:
      description: Queue receipt.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Receipt' }
    Error:
      description: Request error.
      content:
        application/json:
          schema:
            type: object
            required: [error]
            properties:
              error:
                type: object
                required: [message, code]
                properties:
                  message: { type: string }
                  code: { type: string }
  schemas:
    WebhookSettings:
      type: object
      additionalProperties: false
      properties:
        url: { type: string, format: uri, pattern: '^https://' }
        active: { type: boolean }
        events:
          type: array
          minItems: 1
          items:
            enum: [email.accepted, email.delivered, email.delayed, email.bounced, email.rejected, email.complained, email.opened, email.clicked, email.failed, email.suppressed, email.received]
    Receipt:
      type: object
      required: [id, state]
      properties:
        id: { type: string, format: uuid }
        state: { type: string }
    Email:
      allOf:
        - $ref: '#/components/schemas/Receipt'
        - type: object
          properties:
            from: { type: string, format: email }
            to: { type: array, items: { type: string, format: email } }
            subject: { type: string }
            source: { enum: [mock, ses] }
            acceptanceState: { type: string }
            scheduledAt: { type: string, format: date-time }
            events: { type: array, items: { type: object } }
            recipients: { type: array, items: { type: object } }
            attachments: { type: array, items: { type: object } }
    AddressList:
      oneOf:
        - { type: string, format: email }
        - { type: array, minItems: 1, maxItems: 50, items: { type: string, format: email } }
    EmailInput:
      type: object
      additionalProperties: false
      required: [from, to]
      description: Use inline subject and body or a template ID. Use one recipient when tracking is enabled.
      properties:
        from: { type: string, format: email }
        to: { $ref: '#/components/schemas/AddressList' }
        cc: { $ref: '#/components/schemas/AddressList' }
        bcc: { $ref: '#/components/schemas/AddressList' }
        replyTo: { $ref: '#/components/schemas/AddressList' }
        subject: { type: string, maxLength: 998 }
        text: { type: string, maxLength: 500000 }
        html: { type: string, maxLength: 500000 }
        templateId: { type: string, format: uuid }
        templateVersion: { type: integer, minimum: 1 }
        variables:
          type: object
          additionalProperties: { type: [string, number, boolean, 'null'] }
        scheduleAt: { type: string, format: date-time }
        track: { type: boolean }
        attachments:
          type: array
          maxItems: 10
          items:
            type: object
            required: [filename, content]
            properties:
              filename: { type: string, maxLength: 255 }
              content: { type: string, contentEncoding: base64 }
              contentType: { type: string }
