openapi: 3.1.0
info:
  title: Relay API
  version: "1.0.0"
  description: >
    Transactional email at the edge. A plain HTTPS API — no SDK required.
    Authenticate with an API key as a Bearer token.
  license:
    name: Proprietary
servers:
  - url: https://api.relay.dev
security:
  - apiKey: []
tags:
  - name: Email
  - name: Messages
  - name: Suppressions
  - name: Templates
  - name: Webhooks
paths:
  /v1/email:
    post:
      tags: [Email]
      summary: Send an email
      description: >
        Validates, rate-limits (per tenant), checks the suppression list, and
        enqueues the message for delivery via AWS SES. Returns immediately with
        a message id.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SendRequest" }
      responses:
        "202":
          description: Accepted and queued
          content:
            application/json:
              schema:
                type: object
                properties:
                  message_id: { type: string }
                  status: { type: string, enum: [queued, suppressed] }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "422": { $ref: "#/components/responses/ValidationError" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /v1/messages:
    get:
      tags: [Messages]
      summary: List messages
      parameters:
        - { name: limit, in: query, schema: { type: integer, default: 25, maximum: 100 } }
        - { name: cursor, in: query, schema: { type: integer }, description: epoch-ms cursor }
      responses:
        "200":
          description: A page of messages
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Message" } }
                  next_cursor: { type: [integer, "null"] }
  /v1/messages/{id}:
    get:
      tags: [Messages]
      summary: Get a message
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: The message
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Message" }
        "404": { $ref: "#/components/responses/NotFound" }
  /v1/suppressions:
    get:
      tags: [Suppressions]
      summary: List suppressions
      responses:
        "200": { description: A page of suppressions }
    post:
      tags: [Suppressions]
      summary: Add a suppression
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties: { email: { type: string, format: email } }
      responses:
        "201": { description: Created }
        "422": { $ref: "#/components/responses/ValidationError" }
  /v1/suppressions/{email}:
    delete:
      tags: [Suppressions]
      summary: Remove a suppression
      parameters:
        - { name: email, in: path, required: true, schema: { type: string } }
      responses:
        "200": { description: Removed }
  /v1/templates:
    get:
      tags: [Templates]
      summary: List templates
      responses:
        "200": { description: Templates }
    post:
      tags: [Templates]
      summary: Create a template
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [alias, name]
              properties:
                alias: { type: string }
                name: { type: string }
      responses:
        "201": { description: Created }
        "409": { description: Alias already exists }
  /v1/templates/{alias}/versions:
    post:
      tags: [Templates]
      summary: Add a template version
      parameters:
        - { name: alias, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [subject]
              properties:
                subject: { type: string }
                html_body: { type: string }
                text_body: { type: string }
                activate: { type: boolean, default: true }
      responses:
        "201": { description: Created }
  /v1/webhooks:
    get:
      tags: [Webhooks]
      summary: List webhooks
      responses:
        "200": { description: Webhooks }
    post:
      tags: [Webhooks]
      summary: Create a webhook
      description: >
        Returns the signing secret exactly once. Each delivered event carries an
        `X-Webhook-Signature: sha256=<hmac>` header computed over the raw body.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url: { type: string, format: uri }
                events:
                  type: array
                  items: { type: string, enum: [delivery, bounce, complaint] }
      responses:
        "201": { description: Created (includes one-time secret) }
components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: "An API key, e.g. `Authorization: Bearer esk_live_...`"
  schemas:
    SendRequest:
      type: object
      required: [from]
      properties:
        from: { type: string, format: email }
        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: string, format: email }
        subject: { type: string }
        html_body: { type: string }
        text_body: { type: string }
        message_stream: { type: string, enum: [outbound, broadcast], default: outbound }
        tag: { type: string }
        template_alias: { type: string, description: "Render this server-side template instead of subject/body" }
        template_model: { type: object, additionalProperties: true }
    Message:
      type: object
      properties:
        id: { type: string }
        from_addr: { type: string }
        to_addr: { type: string }
        subject: { type: string }
        status: { type: string, enum: [queued, sent, failed, suppressed] }
        ses_message_id: { type: [string, "null"] }
        error: { type: [string, "null"] }
        created_at: { type: integer }
        updated_at: { type: integer }
    Error:
      type: object
      properties:
        error: { type: string, description: "snake_case error code" }
  responses:
    Unauthorized:
      description: Missing or invalid API key
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    ValidationError:
      description: The payload failed validation
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    RateLimited:
      description: Rate limit or daily quota exceeded
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    NotFound:
      description: Not found
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
