mass_mail_engine/docs/openapi.yaml
warrenchen d49c30b447 feat: Add SQS integration for SES event processing
- Introduced SqsSesPollerWorker to poll messages from SQS and process SES events.
- Implemented SesEventProcessingService to handle SES event payloads and store them in the database.
- Updated DevMockSenderWorker to support new SES sending methods and improved logging for unsubscribe headers.
- Added AWS SDK for SQS to project dependencies.
2026-02-26 17:10:25 +09:00

550 lines
14 KiB
YAML

openapi: 3.1.0
info:
title: Send Engine API
version: 0.1.0
description: |
Send Engine external API and webhooks.
servers:
- url: https://send-engine.example.com
security:
- bearerAuth: []
paths:
/api/send-jobs:
post:
summary: Create send job (legacy/internal)
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSendJobRequest'
responses:
'200':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSendJobResponse'
'409':
description: Conflict
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/send-jobs/{id}:
get:
summary: Get send job
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/SendJob'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/send-jobs/{id}/cancel:
post:
summary: Cancel send job
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/SendJobStatusResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/webhooks/subscriptions:
post:
summary: Member Center subscription events
security:
- webhookSignature: []
webhookTimestamp: []
webhookNonce: []
webhookClientId: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SubscriptionEvent'
responses:
'200':
description: Accepted
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
description: Duplicate event
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/webhooks/lists/full-sync:
post:
summary: Member Center full list sync
security:
- webhookSignature: []
webhookTimestamp: []
webhookNonce: []
webhookClientId: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FullSyncBatch'
responses:
'200':
description: Accepted
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/webhooks/ses:
post:
summary: SES/SNS events
security:
- sesSignature: []
requestBody:
required: true
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/SnsEnvelope'
- $ref: '#/components/schemas/SesEvent'
responses:
'200':
description: OK
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: Invalid payload
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal processing error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
webhookSignature:
type: apiKey
in: header
name: X-Signature
webhookTimestamp:
type: apiKey
in: header
name: X-Timestamp
webhookNonce:
type: apiKey
in: header
name: X-Nonce
webhookClientId:
type: apiKey
in: header
name: X-Client-Id
sesSignature:
type: apiKey
in: header
name: X-Amz-Sns-Signature
schemas:
CreateSendJobRequest:
type: object
required: [subject]
description: |
Request accepts both snake_case and camelCase keys.
Recommended contract is snake_case for cross-language consistency.
properties:
tenant_id:
type: string
format: uuid
tenantId:
type: string
format: uuid
list_id:
type: string
format: uuid
listId:
type: string
format: uuid
name:
type: string
subject:
type: string
minLength: 1
body_html:
type: string
bodyHtml:
type: string
body_text:
type: string
bodyText:
type: string
template:
type: object
additionalProperties: true
description: |
Optional template metadata used by sender runtime.
Supported keys:
- ses_template_name: SES template name override
- list_unsubscribe_url_template: URL template, e.g. https://member.example/unsubscribe?token={{unsubscribe_token}}
- list_unsubscribe_mailto: mailto endpoint, e.g. mailto:unsubscribe@member.example
scheduled_at:
type: string
format: date-time
scheduledAt:
type: string
format: date-time
window_start:
type: string
format: date-time
windowStart:
type: string
format: date-time
window_end:
type: string
format: date-time
windowEnd:
type: string
format: date-time
tracking:
$ref: '#/components/schemas/TrackingOptions'
allOf:
- anyOf:
- required: [list_id]
- required: [listId]
- anyOf:
- required: [body_html]
- required: [bodyHtml]
- required: [body_text]
- required: [bodyText]
- required: [template]
CreateSendJobResponse:
type: object
required: [send_job_id, sendJobId, status]
properties:
send_job_id:
type: string
format: uuid
sendJobId:
type: string
format: uuid
status:
type: string
enum: [pending, running, completed, failed, cancelled]
SendJob:
type: object
required: [id, tenant_id, tenantId, list_id, listId, campaign_id, campaignId, status]
properties:
id:
type: string
format: uuid
tenant_id:
type: string
format: uuid
tenantId:
type: string
format: uuid
list_id:
type: string
format: uuid
listId:
type: string
format: uuid
campaign_id:
type: string
format: uuid
campaignId:
type: string
format: uuid
status:
type: string
enum: [pending, running, completed, failed, cancelled]
scheduled_at:
type: string
format: date-time
scheduledAt:
type: string
format: date-time
window_start:
type: string
format: date-time
windowStart:
type: string
format: date-time
window_end:
type: string
format: date-time
windowEnd:
type: string
format: date-time
SendJobStatusResponse:
type: object
required: [id, status]
properties:
id:
type: string
format: uuid
status:
type: string
enum: [pending, running, completed, failed, cancelled]
TrackingOptions:
type: object
properties:
open:
type: boolean
click:
type: boolean
SubscriptionEvent:
type: object
required: [event_id, event_type, tenant_id, list_id, subscriber, occurred_at]
properties:
event_id:
type: string
format: uuid
event_type:
type: string
enum: [subscription.activated, subscription.unsubscribed, preferences.updated]
tenant_id:
type: string
format: uuid
list_id:
type: string
format: uuid
subscriber:
$ref: '#/components/schemas/SubscriberPayload'
occurred_at:
type: string
format: date-time
SubscriberPayload:
type: object
required: [id, email, status]
properties:
id:
type: string
format: uuid
email:
type: string
format: email
status:
type: string
enum: [active, unsubscribed, bounced, complaint, suppressed]
preferences:
type: object
additionalProperties: true
FullSyncBatch:
type: object
required: [sync_id, batch_no, batch_total, tenant_id, list_id, subscribers, occurred_at]
properties:
sync_id:
type: string
format: uuid
batch_no:
type: integer
minimum: 1
batch_total:
type: integer
minimum: 1
tenant_id:
type: string
format: uuid
list_id:
type: string
format: uuid
subscribers:
type: array
items:
$ref: '#/components/schemas/SubscriberPayload'
occurred_at:
type: string
format: date-time
SesEvent:
type: object
required: [event_type, message_id, tenant_id, email, occurred_at]
properties:
event_type:
type: string
enum: [bounce, hard_bounced, soft_bounced, complaint, suppression, delivery, open, click]
message_id:
type: string
tenant_id:
type: string
format: uuid
email:
type: string
format: email
bounce_type:
type: string
enum: [hard, soft]
occurred_at:
type: string
format: date-time
SnsEnvelope:
type: object
required: [Type, MessageId, Message, Timestamp]
properties:
Type:
type: string
enum: [Notification, SubscriptionConfirmation, UnsubscribeConfirmation]
MessageId:
type: string
TopicArn:
type: string
Subject:
type: string
Message:
type: string
description: JSON string containing SES event payload
Timestamp:
type: string
format: date-time
ErrorResponse:
type: object
required: [error]
properties:
error:
type: string
message:
type: string
reason:
type: string
request_id:
type: string