- 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.
550 lines
14 KiB
YAML
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
|