# Install - 需求:.NET SDK 8.x, PostgreSQL - 設定:複製 `.env.example` → `.env` - AWS Credential(SQS/SES): - Local / Docker 開發: - 設定 `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`(若為臨時憑證再加 `AWS_SESSION_TOKEN`) - 設定 `AWS__Region`(或 `Sqs__Region` / `Ses__Region`) - 建議設 `AWS_EC2_METADATA_DISABLED=true`,避免本機誤打 EC2 metadata - ECS 正式環境(建議): - 不要放 Access Key,改用 ECS Task Role(`taskRoleArn`) - Task Role 至少授權: - `sqs:ReceiveMessage` - `sqs:DeleteMessage` - `sqs:GetQueueAttributes` - `ses:SendEmail`(raw_bulk) - `ses:SendBulkEmail`(bulk_template,若有使用) - ECS Task Definition 可設定環境變數: - `AWS_REGION=ap-northeast-1`(或直接用 `.env` 的 `AWS__Region`) - Migration: - 預設由 API 啟動時自動執行(`Db__AutoMigrate=true`) - 需要關閉時請設定 `Db__AutoMigrate=false` - 手動執行可用 `dotnet run --project src/SendEngine.Installer -- migrate` - Webhook Auth 初始化(不使用 SQL 檔,改用 Installer): - 若僅需先建立 tenant 基本資料: - `dotnet run --project src/SendEngine.Installer -- ensure-tenant --tenant-id [--tenant-name ]` - 使用 Installer 建立 webhook client(`id` 自動隨機產生): - `dotnet run --project src/SendEngine.Installer -- add-webhook-client --tenant-id [--tenant-name ] --client-id --name --scopes ` - 例如:`dotnet run --project src/SendEngine.Installer -- add-webhook-client --tenant-id 11111111-1111-1111-1111-111111111111 --tenant-name "Tenant A" --client-id member-center-webhook --name "Member Center Webhook" --scopes newsletter:events.write` - 若 tenant 不存在,Installer 會先自動建立 `tenants` 基本資料,避免 webhook 出現 `tenant_not_found` - 建立成功後,Member Center webhook header `X-Client-Id` 請帶回傳的 `id` - 若要自動同步到 Member Center `POST /integrations/send-engine/webhook-clients/upsert`(保留原手動流程): - `dotnet run --project src/SendEngine.Installer -- add-webhook-client --tenant-id --client-id --name --scopes --upsert-member-center --mc-base-url --mc-client-id --mc-client-secret --mc-scope newsletter:events.write.global` - 可選參數: - `--mc-token-path`(預設 `/oauth/token`) - `--mc-upsert-path`(預設 `/integrations/send-engine/webhook-clients/upsert`) - `--mc-token-url` / `--mc-upsert-url`(使用完整 URL 時可覆蓋 path 組合) - Webhook 驗證規則為 tenant 綁定:`auth_clients.tenant_id` 必須等於 payload `tenant_id` - 不支援 `X-Client-Id` fallback - 預設拒絕 `tenant_id = NULL` 的通用 client(`Webhook__AllowNullTenantClient=false`) - Member Center 回寫授權(建議): - `MemberCenter__BaseUrl`(建議) - `MemberCenter__DisableSubscriptionPath`(預設 `/subscriptions/disable`) - `MemberCenter__TokenPath`(預設 `/oauth/token`) - `MemberCenter__OneClickUnsubscribeTokensPath`(預設 `/newsletter/one-click-unsubscribe-tokens`) - `MemberCenter__ClientId` - `MemberCenter__ClientSecret` - `MemberCenter__Scope=newsletter:events.write` - `MemberCenter__ApiToken` 僅作暫時 fallback(非首選) - Send Job API 驗證(JWT): - `Jwt__Issuer` - `Jwt__Audience` - 建議(Member Center OIDC/JWKS): - `Jwt__Authority`(例如 `http://member-center`) - 或 `Jwt__MetadataAddress`(例如 `http://member-center/.well-known/openid-configuration`) - 若兩者都未設定,會自動回退使用 `MemberCenter__BaseUrl + /.well-known/openid-configuration` - `Jwt__RequireHttpsMetadata`(本機可設 `false`) - 相容舊模式(不建議):`Jwt__SigningKey`(HS 對稱驗簽) - 本機測試輔助(臨時): - `TestFriendly__Enabled=true` 時: - webhook 收到未知 tenant 會自動建立 tenant - `/webhooks/ses` 不做任何 DB 存取,但會保留 Member Center callback 流程(僅用於測試流程打通) - Dev Sender(Mock 發信): - `DevSender__Enabled=true`:背景 worker 會處理 `pending` send jobs,並將每位收件人的預計發送內容寫入 `events_inbox`(`source=dev_sender`, `event_type=send.preview`) - `DevSender__PollIntervalSeconds`:輪詢間隔秒數(預設 5) - `ESP__Provider=ses` 時,即使 `DevSender__Enabled=false`,背景 sender 仍會啟動並改用 SES 發送(模式由 `Ses__SendMode` 決定) - SES 相關參數:`Ses__Region`、`Ses__FromEmail`、`Ses__ConfigurationSet`(可選)、`Ses__SendMode`、`Ses__TemplateName` - `Ses__SendMode=raw_bulk`(預設):使用 SES `SendEmail`,依內容分組後每次最多 50 位收件者(不依賴 SES Template) - `Ses__SendMode=bulk_template`:使用 SES `SendBulkEmail` + Template(需提供 `template.ses_template_name` 或 `Ses__TemplateName`) - SES 發送時會附帶 message tags:`tenant_id`、`list_id`、`campaign_id`、`send_job_id` - SQS Poller(SES 事件回流): - `Sqs__Enabled=true` - `Sqs__QueueUrl=` - `Sqs__Region`(未設定時回退 `AWS__Region` 再回退 `Ses__Region`) - `Sqs__PollWaitSeconds`(預設 20) - `Sqs__MaxMessages`(預設 10) - `Sqs__VisibilityTimeoutSeconds`(預設 30) - SQS poller 目前未提供獨立 DLQ worker;可先用 AWS SQS redrive policy 管理失敗訊息 - `SendBulkEmail` 會使用 SES 模板名稱: - 先讀 `campaign.template.ses_template_name` - 若未提供則回退 `Ses__TemplateName` - 若設定了 Member Center one-click token endpoint,sender 會在發送前批次呼叫 `/newsletter/one-click-unsubscribe-tokens`,僅發送 `status=issued` 的收件者 - 內容替換合約(Mock 與 SES 共用): - `{{email}}` - `{{unsubscribe_token}}` - `{{tenant_id}}` / `{{list_id}}` / `{{campaign_id}}` / `{{send_job_id}}` - 若在 `campaign.template` 提供: - `list_unsubscribe_url_template` - `list_unsubscribe_mailto` - sender 會自動加上 `List-Unsubscribe` 與 `List-Unsubscribe-Post` headers - 正式環境建議維持 `false`