openapi: 3.1.0
info:
  title: Alweo Sending API
  version: "1.0.0"
  description: |
    Send email from your own systems through Alweo, DKIM-signed with your own domain.
    Full documentation: https://alweo.se/api-dokumentation/en (Swedish: https://alweo.se/api-dokumentation).

    Keys are created in the Alweo panel under Email → Sending API. Each key belongs to one domain
    and can only send from addresses on that domain. Test keys (`alw_test_…`) validate everything
    as usual but send nothing.
  contact:
    email: hello@alweo.se
servers:
  - url: https://alweo.se/api/v1
security:
  - bearerAuth: []
paths:
  /emails:
    post:
      operationId: sendEmail
      summary: Send an email
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendEmailRequest"
            example:
              from: "Web shop <order@yourcompany.se>"
              to: ["customer@example.com"]
              subject: "Thanks for your order"
              html: "<p>We have received your order.</p>"
              text: "We have received your order."
      responses:
        "200":
          description: Accepted by the mail server for delivery (or validated, for a test key).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SendEmailResponse"
        "401": { $ref: "#/components/responses/Error" }
        "402": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
        "500": { $ref: "#/components/responses/Error" }
        "502": { $ref: "#/components/responses/Error" }
        "503": { $ref: "#/components/responses/Error" }
  /emails/{id}:
    get:
      operationId: getEmail
      summary: Get an email and its delivery status per recipient
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: The email. Delivery status is updated about every two minutes and followed for three days.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Email"
        "401": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
webhooks:
  emailEvent:
    post:
      summary: Event sent to your webhook URL
      description: |
        Signed with the header `Alweo-Signature: t=<unix seconds>,v1=<hex>` where v1 is
        HMAC-SHA256 of `t + "." + raw body` using the webhook secret (`whsec_…`).
        Respond with 2xx within 10 seconds. Failed deliveries are retried after 1, 5, 15, 60
        and 180 minutes (at most six attempts). Events may arrive more than once and out of order.
      parameters:
        - name: Alweo-Signature
          in: header
          required: true
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEvent"
      responses:
        "200":
          description: Received.
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "API key starting with alw_ (or alw_test_ for test keys)."
  responses:
    Error:
      description: Error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
  schemas:
    AddressList:
      oneOf:
        - type: string
        - type: array
          items: { type: string }
    Attachment:
      type: object
      required: [filename, content]
      properties:
        filename: { type: string }
        content: { type: string, description: "File content, base64 encoded." }
        content_type: { type: string, example: application/pdf }
    SendEmailRequest:
      type: object
      required: [from, to, subject]
      description: "At least one of html or text is required. 1–50 recipients in total across to, cc and bcc. At most 20 attachments and 10 MB in total."
      properties:
        from: { type: string, description: "Address on the key's domain, optionally with a name: \"Name <address>\"." }
        to: { $ref: "#/components/schemas/AddressList" }
        cc: { $ref: "#/components/schemas/AddressList" }
        bcc: { $ref: "#/components/schemas/AddressList" }
        subject: { type: string, maxLength: 900 }
        html: { type: string }
        text: { type: string }
        reply_to: { type: string }
        headers:
          type: object
          additionalProperties: { type: string }
        attachments:
          type: array
          maxItems: 20
          items: { $ref: "#/components/schemas/Attachment" }
    SendEmailResponse:
      type: object
      required: [id]
      properties:
        id: { type: string, format: uuid }
        test: { type: boolean, description: "Present and true when sent with a test key (nothing was sent)." }
        suppressed:
          type: array
          items: { type: string }
          description: "Recipients removed because they are on the suppression list."
    Delivery:
      type: object
      properties:
        status: { type: string, enum: [delivered, delivery_delayed, bounced] }
        detail: { type: string }
        at: { type: string, format: date-time }
    Email:
      type: object
      properties:
        id: { type: string, format: uuid }
        from: { type: string }
        recipients: { type: integer }
        subject: { type: string }
        status: { type: string, enum: [sent, failed] }
        test: { type: boolean }
        error: { type: ["string", "null"] }
        created_at: { type: string, format: date-time }
        delivery:
          type: object
          additionalProperties: { $ref: "#/components/schemas/Delivery" }
    WebhookEvent:
      type: object
      required: [type, created_at, data]
      properties:
        type:
          type: string
          enum: [email.sent, email.delivered, email.delivery_delayed, email.bounced, email.suppressed, email.test]
        created_at: { type: string, format: date-time }
        data:
          type: object
          properties:
            email_id: { type: string }
            from: { type: string }
            to:
              oneOf:
                - type: string
                - type: array
                  items: { type: string }
            subject: { type: string }
            detail: { type: string }
            reason: { type: string }
            test: { type: boolean }
            message: { type: string }
            webhook_id: { type: string }
    Error:
      type: object
      required: [statusCode, name, message]
      properties:
        statusCode: { type: integer }
        name:
          type: string
          enum: [unauthorized, trial_ended, invalid_from, not_found, validation_error, recipients_suppressed,
                 rate_limit_exceeded, monthly_quota_exceeded, internal_error, send_failed, not_configured]
        message: { type: string }
