726 lines
20 KiB
YAML
726 lines
20 KiB
YAML
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
|