feat: mail service
This commit is contained in:
@@ -0,0 +1,725 @@
|
||||
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
|
||||
Reference in New Issue
Block a user