openapi: 3.0.3 info: title: Galaxy Mail Service Internal REST API version: v1 description: | This specification documents the trusted internal REST contract of `galaxy/mail`. The current document freezes: - the dedicated auth-delivery route used by `Auth / Session Service`; - the trusted operator read and resend routes used for delivery audit and recovery. Contract rules: - the internal surface lives under `/api/v1/internal`; - request and response bodies are JSON only; - auth-delivery intake requires the `Idempotency-Key` header; - request bodies use strict JSON decoding with unknown-field rejection; - trailing JSON input is rejected; - success outcomes are limited to `sent` and `suppressed` on the auth route; - mismatched replays on the same `Idempotency-Key` return `409 conflict`; - operator listing order is `created_at_ms DESC`, then `delivery_id DESC`; - operator list cursors are opaque base64url encodings of `created_at_ms:delivery_id`; - `sent` means durable acceptance into the mail-delivery pipeline rather than immediate SMTP completion; - auth callers must not automatically retry transport or upstream failures; - `Auth / Session Service` sends the created `challenge_id` as the raw `Idempotency-Key` header value. servers: - url: http://localhost:8080 description: Default local internal listener for Mail Service. tags: - name: AuthIntegration description: Trusted auth-facing mail-delivery intake frozen for `Auth / Session Service`. - name: OperatorIntegration description: Trusted operator-facing delivery reads and resend controls. paths: /api/v1/internal/login-code-deliveries: post: tags: - AuthIntegration operationId: acceptLoginCodeDelivery summary: Accept one auth login-code delivery request description: | Validates one trusted auth login-code delivery request and accepts it durably into the internal mail-delivery pipeline or intentionally suppresses outward delivery while keeping the auth flow success-shaped. parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/LoginCodeDeliveryRequest" responses: "200": description: Stable auth-delivery acceptance outcome. content: application/json: schema: $ref: "#/components/schemas/LoginCodeDeliveryResponse" "400": $ref: "#/components/responses/InvalidRequestError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/internal/deliveries: get: tags: - OperatorIntegration operationId: listDeliveries summary: List durable deliveries for trusted operators description: | Returns one deterministic page of brief delivery summaries ordered by `created_at_ms DESC`, then `delivery_id DESC`. parameters: - $ref: "#/components/parameters/RecipientFilter" - $ref: "#/components/parameters/StatusFilter" - $ref: "#/components/parameters/SourceFilter" - $ref: "#/components/parameters/TemplateIDFilter" - $ref: "#/components/parameters/IdempotencyKeyFilter" - $ref: "#/components/parameters/FromCreatedAtMSFilter" - $ref: "#/components/parameters/ToCreatedAtMSFilter" - $ref: "#/components/parameters/ListLimit" - $ref: "#/components/parameters/ListCursor" responses: "200": description: One deterministic page of delivery summaries. content: application/json: schema: $ref: "#/components/schemas/DeliveryListResponse" "400": $ref: "#/components/responses/InvalidRequestError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/internal/deliveries/{delivery_id}: get: tags: - OperatorIntegration operationId: getDelivery summary: Get one durable delivery for trusted operators parameters: - $ref: "#/components/parameters/DeliveryIDPath" responses: "200": description: One full delivery view with the optional dead-letter entry. content: application/json: schema: $ref: "#/components/schemas/DeliveryDetailResponse" "400": $ref: "#/components/responses/InvalidRequestError" "404": $ref: "#/components/responses/DeliveryNotFoundError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/internal/deliveries/{delivery_id}/attempts: get: tags: - OperatorIntegration operationId: listDeliveryAttempts summary: Get the attempt history of one durable delivery parameters: - $ref: "#/components/parameters/DeliveryIDPath" responses: "200": description: The ordered attempt history of one durable delivery. content: application/json: schema: $ref: "#/components/schemas/DeliveryAttemptsResponse" "400": $ref: "#/components/responses/InvalidRequestError" "404": $ref: "#/components/responses/DeliveryNotFoundError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/internal/deliveries/{delivery_id}/resend: post: tags: - OperatorIntegration operationId: resendDelivery summary: Clone one terminal delivery for resend parameters: - $ref: "#/components/parameters/DeliveryIDPath" responses: "200": description: The clone delivery was created successfully. content: application/json: schema: $ref: "#/components/schemas/DeliveryResendResponse" "400": $ref: "#/components/responses/InvalidRequestError" "404": $ref: "#/components/responses/DeliveryNotFoundError" "409": $ref: "#/components/responses/ResendNotAllowedError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" components: parameters: IdempotencyKey: name: Idempotency-Key in: header required: true description: | Caller-owned stable deduplication key. `Auth / Session Service` uses the created `challenge_id` as the raw header value. schema: type: string DeliveryIDPath: name: delivery_id in: path required: true description: Mail Service delivery identifier. schema: type: string RecipientFilter: name: recipient in: query required: false description: Effective-recipient filter covering `to`, `cc`, and `bcc`. schema: type: string StatusFilter: name: status in: query required: false description: Delivery lifecycle status filter. schema: type: string enum: - queued - rendered - sending - sent - suppressed - failed - dead_letter SourceFilter: name: source in: query required: false description: Delivery source filter. schema: type: string enum: - authsession - notification - operator_resend TemplateIDFilter: name: template_id in: query required: false description: Template family filter. schema: type: string IdempotencyKeyFilter: name: idempotency_key in: query required: false description: | Idempotency-key filter. When `source` is omitted, Mail Service matches the key across all frozen sources. schema: type: string FromCreatedAtMSFilter: name: from_created_at_ms in: query required: false description: Inclusive lower bound for `created_at_ms`. schema: type: integer format: int64 ToCreatedAtMSFilter: name: to_created_at_ms in: query required: false description: Inclusive upper bound for `created_at_ms`. schema: type: integer format: int64 ListLimit: name: limit in: query required: false description: | Maximum number of returned deliveries. The frozen default is `50` and the maximum is `200`. schema: type: integer minimum: 1 maximum: 200 ListCursor: name: cursor in: query required: false description: | Opaque continuation cursor encoded as base64url of `created_at_ms:delivery_id`. schema: type: string schemas: LoginCodeDeliveryRequest: type: object additionalProperties: false required: - email - code - locale properties: email: type: string description: Normalized destination e-mail address. code: type: string description: Exact login code generated by `Auth / Session Service`. locale: type: string description: Canonical BCP 47 language tag already resolved upstream. LoginCodeDeliveryResponse: type: object additionalProperties: false required: - outcome properties: outcome: type: string description: Stable coarse outcome of the auth-delivery acceptance. enum: - sent - suppressed DeliverySummaryResponse: type: object additionalProperties: false required: - delivery_id - source - payload_mode - to - cc - bcc - reply_to - locale_fallback_used - idempotency_key - status - attempt_count - created_at_ms - updated_at_ms properties: delivery_id: type: string resend_parent_delivery_id: type: string source: type: string enum: - authsession - notification - operator_resend payload_mode: type: string enum: - rendered - template template_id: type: string to: type: array items: type: string cc: type: array items: type: string bcc: type: array items: type: string reply_to: type: array items: type: string locale: type: string locale_fallback_used: type: boolean idempotency_key: type: string status: type: string enum: - queued - rendered - sending - sent - suppressed - failed - dead_letter attempt_count: type: integer last_attempt_status: type: string enum: - scheduled - in_progress - render_failed - provider_accepted - provider_rejected - transport_failed - timed_out provider_summary: type: string created_at_ms: type: integer format: int64 updated_at_ms: type: integer format: int64 sent_at_ms: type: integer format: int64 suppressed_at_ms: type: integer format: int64 failed_at_ms: type: integer format: int64 dead_lettered_at_ms: type: integer format: int64 DeliveryListResponse: type: object additionalProperties: false required: - items properties: items: type: array items: $ref: "#/components/schemas/DeliverySummaryResponse" next_cursor: type: string AttachmentResponse: type: object additionalProperties: false required: - filename - content_type - size_bytes properties: filename: type: string content_type: type: string size_bytes: type: integer format: int64 DeadLetterResponse: type: object additionalProperties: false required: - delivery_id - final_attempt_no - failure_classification - created_at_ms properties: delivery_id: type: string final_attempt_no: type: integer failure_classification: type: string provider_summary: type: string created_at_ms: type: integer format: int64 recovery_hint: type: string DeliveryDetailResponse: type: object additionalProperties: false required: - delivery_id - source - payload_mode - to - cc - bcc - reply_to - attachments - locale_fallback_used - idempotency_key - status - attempt_count - created_at_ms - updated_at_ms properties: delivery_id: type: string resend_parent_delivery_id: type: string source: type: string enum: - authsession - notification - operator_resend payload_mode: type: string enum: - rendered - template template_id: type: string template_variables: type: object additionalProperties: true to: type: array items: type: string cc: type: array items: type: string bcc: type: array items: type: string reply_to: type: array items: type: string subject: type: string text_body: type: string html_body: type: string attachments: type: array items: $ref: "#/components/schemas/AttachmentResponse" locale: type: string locale_fallback_used: type: boolean idempotency_key: type: string status: type: string enum: - queued - rendered - sending - sent - suppressed - failed - dead_letter attempt_count: type: integer last_attempt_status: type: string enum: - scheduled - in_progress - render_failed - provider_accepted - provider_rejected - transport_failed - timed_out provider_summary: type: string created_at_ms: type: integer format: int64 updated_at_ms: type: integer format: int64 sent_at_ms: type: integer format: int64 suppressed_at_ms: type: integer format: int64 failed_at_ms: type: integer format: int64 dead_lettered_at_ms: type: integer format: int64 dead_letter: $ref: "#/components/schemas/DeadLetterResponse" AttemptResponse: type: object additionalProperties: false required: - delivery_id - attempt_no - scheduled_for_ms - status properties: delivery_id: type: string attempt_no: type: integer scheduled_for_ms: type: integer format: int64 started_at_ms: type: integer format: int64 finished_at_ms: type: integer format: int64 status: type: string enum: - scheduled - in_progress - render_failed - provider_accepted - provider_rejected - transport_failed - timed_out provider_classification: type: string provider_summary: type: string DeliveryAttemptsResponse: type: object additionalProperties: false required: - items properties: items: type: array items: $ref: "#/components/schemas/AttemptResponse" DeliveryResendResponse: type: object additionalProperties: false required: - delivery_id properties: delivery_id: type: string ErrorResponse: type: object additionalProperties: false required: - error properties: error: $ref: "#/components/schemas/ErrorBody" ErrorBody: type: object additionalProperties: false required: - code - message properties: code: type: string description: Stable internal API error code. message: type: string description: Human-readable trusted error message. responses: InvalidRequestError: description: Request validation failed. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: missingHeader: value: error: code: invalid_request message: Idempotency-Key header must not be empty invalidCursor: value: error: code: invalid_request message: cursor is invalid ConflictError: description: The current idempotency scope belongs to a different normalized request. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: conflict: value: error: code: conflict message: request conflicts with current state DeliveryNotFoundError: description: The requested delivery does not exist. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: missingDelivery: value: error: code: delivery_not_found message: delivery not found ResendNotAllowedError: description: The requested delivery is not in a terminal resend-eligible state. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: resendNotAllowed: value: error: code: resend_not_allowed message: delivery status does not allow resend InternalError: description: Internal application invariant failed. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: internal: value: error: code: internal_error message: internal server error ServiceUnavailableError: description: Durable acceptance or trusted delivery inspection could not be completed. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: unavailable: value: error: code: service_unavailable message: service is unavailable