openapi: 3.1.0
info:
  title: Phefo Mail API
  version: 1.0.0
  description: >
    Transactional email submission API. A sent status means SMTP acceptance, not confirmed inbox
    delivery.
servers:
  - url: https://mail.web.co.ls/api
    description: Production
  - url: http://localhost:3000/api
    description: Local development
security:
  - projectKey: []
paths:
  /v1/messages:
    post:
      operationId: queueMessage
      summary: Validate, persist, and queue a message
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendMessage'
            examples:
              direct:
                value:
                  from: receipts@example.com
                  to: [customer@example.net]
                  subject: Receipt 1042
                  html: <h1>Thanks for your order</h1>
                  text: Thanks for your order
                  idempotencyKey: order-1042-receipt-v1
              template:
                value:
                  from: hello@example.com
                  to: customer@example.net
                  templateId: 7
                  templateData: { name: Mpho }
      responses:
        '202':
          description: Message accepted into the durable queue
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: object
                    required: [id, status]
                    properties:
                      id: { oneOf: [{ type: integer }, { type: string }] }
                      status: { const: queued }
        '200':
          description: An existing message returned for an idempotent replay
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data:
                    type: object
                    required: [id, status]
                    properties:
                      id: { oneOf: [{ type: integer }, { type: string }] }
                      status: { enum: [queued, sending, sent, failed] }
                  meta:
                    type: object
                    required: [idempotentReplay]
                    properties:
                      idempotentReplay: { const: true }
        '401': { $ref: '#/components/responses/Error' }
        '403': { $ref: '#/components/responses/Error' }
        '422': { $ref: '#/components/responses/Error' }
        '429': { $ref: '#/components/responses/Error' }
        '500': { $ref: '#/components/responses/Error' }
components:
  securitySchemes:
    projectKey:
      type: http
      scheme: bearer
      bearerFormat: Phefo project API key
  responses:
    Error:
      description: Structured error
      content:
        application/json:
          schema:
            type: object
            required: [error]
            properties:
              error:
                type: object
                required: [code, message]
                properties:
                  code: { type: string }
                  message: { type: string }
                  details: {}
  schemas:
    SendMessage:
      type: object
      required: [from, to]
      oneOf:
        - title: Direct content
          required: [subject]
          anyOf:
            - required: [html]
            - required: [text]
          not:
            required: [templateId]
        - title: Saved template
          required: [templateId]
          not:
            anyOf:
              - required: [html]
              - required: [text]
      properties:
        from: { type: string, format: email }
        to:
          oneOf:
            - { type: string, format: email }
            - { type: array, minItems: 1, maxItems: 100, items: { type: string, format: email } }
        cc:
          oneOf:
            - { type: string, format: email }
            - { type: array, maxItems: 100, items: { type: string, format: email } }
        bcc:
          oneOf:
            - { type: string, format: email }
            - { type: array, maxItems: 100, items: { type: string, format: email } }
        replyTo: { type: string, format: email }
        subject: { type: string, maxLength: 998 }
        html: { type: string, maxLength: 2000000 }
        text: { type: string, maxLength: 2000000 }
        templateId: { oneOf: [{ type: integer }, { type: string }] }
        templateData:
          type: object
          additionalProperties:
            type: [string, number, boolean, 'null']
        idempotencyKey: { type: string, minLength: 8, maxLength: 160 }
        attachments:
          type: array
          maxItems: 10
          items:
            type: object
            required: [filename, contentType, contentBase64]
            properties:
              filename: { type: string, maxLength: 255 }
              contentType: { type: string }
              contentBase64: { type: string, contentEncoding: base64 }
