feat(v1.1.7-email-inbound): webhook receiver + email:receive trigger
Inbound email: a provider POSTs a normalized JSON message to
POST /api/v1/email-inbound/{app_id}/{trigger_id}; the public receiver
verifies the optional HMAC signature, builds a TriggerEvent::Email, and
enqueues an outbox row the dispatcher delivers like any async trigger.
Handlers see ctx.event.email = #{from,to,cc,subject,text,html,
received_at,message_id}.
- migration 0024: widen triggers.kind + outbox.source_kind CHECKs to
'email'; new email_trigger_details table.
- TriggerKind::Email, TriggerDetails::Email{has_inbound_secret},
OutboxSourceKind::Email, TriggerEvent::Email; dispatcher routes the
email row via the generic resolve_trigger path.
- Admin POST /apps/{id}/triggers/email (validate_trigger_target; module
+ cross-app rejection). inbound_secret is stored ENCRYPTED via the
master key (deviation from the brief's plaintext default; decrypted
per inbound request — see HANDBACK §7).
- Dashboard: email trigger form on the Triggers tab + webhook URL +
expected-payload help.
- 8 DB-gated e2e tests (202/401/404/422/cross-app/handler-fire) +
receiver unit tests (HMAC verify, secret round-trip, payload parse).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
32
crates/manager-core/migrations/0024_email_triggers.sql
Normal file
32
crates/manager-core/migrations/0024_email_triggers.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- v1.1.7: inbound email triggers (email:receive).
|
||||
--
|
||||
-- A configured provider (Mailgun / Postmark / SendGrid / SES) POSTs
|
||||
-- inbound email to POST /api/v1/email-inbound/{app_id}/{trigger_id};
|
||||
-- the receiver normalizes it into a TriggerEvent::Email and enqueues an
|
||||
-- outbox row for the trigger's handler. v1.1.7 ships the webhook path;
|
||||
-- a native SMTP listener is v1.3+.
|
||||
|
||||
-- Widen the trigger-kind + outbox-source CHECK constraints to admit
|
||||
-- 'email'.
|
||||
ALTER TABLE triggers DROP CONSTRAINT triggers_kind_check;
|
||||
ALTER TABLE triggers ADD CONSTRAINT triggers_kind_check
|
||||
CHECK (kind IN ('kv', 'dead_letter', 'docs', 'cron',
|
||||
'files', 'pubsub', 'email'));
|
||||
|
||||
ALTER TABLE outbox DROP CONSTRAINT outbox_source_kind_check;
|
||||
ALTER TABLE outbox ADD CONSTRAINT outbox_source_kind_check
|
||||
CHECK (source_kind IN ('http', 'kv', 'dead_letter', 'docs',
|
||||
'cron', 'files', 'pubsub', 'email'));
|
||||
|
||||
-- Per-trigger inbound config. The HMAC secret used to verify provider
|
||||
-- signatures is stored ENCRYPTED at rest (AES-256-GCM under the process
|
||||
-- master key) — a deviation from the original brief's plaintext column,
|
||||
-- chosen to keep all operationally-secret material encrypted. The
|
||||
-- receiver decrypts it per inbound request. NULL columns mean the
|
||||
-- trigger has no signature verification (accepts any POST to its URL —
|
||||
-- relies on URL secrecy).
|
||||
CREATE TABLE email_trigger_details (
|
||||
trigger_id UUID PRIMARY KEY REFERENCES triggers(id) ON DELETE CASCADE,
|
||||
inbound_secret_encrypted BYTEA, -- ciphertext incl. GCM auth tag (NULL = unsigned)
|
||||
inbound_secret_nonce BYTEA -- 12 bytes (NULL = unsigned)
|
||||
);
|
||||
Reference in New Issue
Block a user