End-to-end ship for outlook V2 ingress + the missing CONTACT_UPDATED trigger + the folder_id config on the existing new-message trigger.
OUTLOOK_CONTACT_UPDATED_TRIGGER (Microsoft Graph subscription on /me/contacts, changeType=updated).OUTLOOK_MESSAGE_TRIGGER gains folder_id config (default "inbox"; accepts any well-known folder name or folder ID). Setup-time access check via Microsoft Graph for non-default folders, so a typo / missing scope / shared-mailbox surfaces with the verbatim Graph error rather than a downstream 404.OutlookBaseTrigger:
OutlookV1CompatTrigger — for the 5 pre-cutover slugs that need to handle both V1 batched (body.value=[...]) and V2 post-split (body=<single notification>) ingress shapes.OutlookV2Trigger — for slugs introduced post-cutover; direct V2-shape access, no iter-and-find._odata_type, _change_type, _event_prefix) and use the mercury generic re-parameterization pattern (BaseClass[Config, Payload]). Drops the _payload = ... / _config = ... underscore workaround.setup() generates a random per-subscription clientState via secrets.token_urlsafe(32) and returns it via SetupData.webhook_signing_secret. Apollo persists it on the per-instance webhook_endpoints.encryptedData row for token_match signature verification.signature: token_match against ${setupFields.webhook_signing_secret} and json_body.value.0.clientState (Microsoft echoes the per-subscription clientState inside every batch item).event_parser: batched on json_body.value — apollo splits the Microsoft batch and forwards each notification as its own trigger_message.handshake: outlook_validation for the Microsoft validationToken handshake.registration_type: user_level.LINEAR_ISSUE_CREATED_TRIGGER, LINEAR_ISSUE_UPDATED_TRIGGER, LINEAR_COMMENT_EVENT_TRIGGER as status = "deprecated" (superseded by per-team-visibility variants from the prior PR; deprecation marker was missed in that PR).apps/outlook/WEBHOOK_TRIGGER_AUTHZ.md.Hermes PR https://github.com/ComposioHQ/hermes/pull/10094 (json_body.value.<n>.<field> array indexing in field_resolver.ts) MUST land before this mercury bundle is pushed. Without it, apollo cannot resolve the signature path and every new trigger-instance create returns 401 at Microsoft's validation handshake.
Driven against connection ca_PhHyFDXQwDT2 on the local stack with cloudflared tunnel. Per-trigger real Microsoft Graph emissions via raw Graph API (with the decrypted bearer), receiver capture observed at the customer webhook URL.
| Trigger slug | Real action | Receiver capture | data.event_type |
|---|---|---|---|
OUTLOOK_MESSAGE_TRIGGER | POST /me/mailFolders/inbox/messages | ✅ | message.created |
OUTLOOK_SENT_MESSAGE_TRIGGER | POST /me/sendMail | ✅ | message.created |
OUTLOOK_EVENT_TRIGGER | POST /me/events | ✅ | event.created |
OUTLOOK_EVENT_CHANGE_TRIGGER | POST / PATCH / DELETE /me/events | ✅ (one capture per change) | event.{created,updated,deleted} |
OUTLOOK_CONTACT_TRIGGER | POST /me/contacts | ✅ | contact.created |
OUTLOOK_CONTACT_UPDATED_TRIGGER | PATCH /me/contacts/{id} | ✅ | contact.updated |
OUTLOOK_MESSAGE_TRIGGER)| Instance | trigger_config.folder_id | Microsoft subscription resource | Fired when message landed in inbox? | Fired when message landed in sentitems? |
|---|---|---|---|---|
ti_80ZRk4PZpW4j | default ("inbox") | /me/mailFolders('inbox')/messages | ✅ | ❌ (correct — wrong folder) |
ti_hLKyHQ6plLnz | "sentitems" | /me/mailFolders('sentitems')/messages | ❌ (correct — wrong folder) | ✅ |
| Test | Apollo ingress response | Receiver captures |
|---|---|---|
POST value=[n1, n2, n3] with valid clientState | 200 | 3 captures with distinct data.id (batch-1, batch-2, batch-3) |
| POST with wrong clientState | 401 (signature fail-closed) | 0 |
POST with empty value=[] | 400 (signature path resolves to undefined) | 0 |
No 5xx in /tmp/hermes-local.log or /tmp/mercury-local.log for any trigger fire.
ca_PhHyFDXQwDT2) into the local hermes DB.--push-bundles --bundler-target json/source --bundler-target json/compiled).OVERWRITE_APOLLO_URL=<cloudflared-tunnel> (per-process env override, not persisted to shared doppler).OUTLOOK_MESSAGE_TRIGGER with folder_id=sentitems.GET /v1.0/subscriptions/{id}: correct resource + changeType + notificationUrl pointing at the V2 ingress URL.references/synthetic-events-rule.md).🤖 Generated with Claude Code