Skip to content

feat(mail): wire Message_Id_Util into Email_Threading + add signed Reply-To#32

Open
mpge wants to merge 1 commit intofeat/email-message-idfrom
feat/email-service-wireup
Open

feat(mail): wire Message_Id_Util into Email_Threading + add signed Reply-To#32
mpge wants to merge 1 commit intofeat/email-message-idfrom
feat/email-service-wireup

Conversation

@mpge
Copy link
Copy Markdown
Member

@mpge mpge commented Apr 24, 2026

Summary

Refactors Email_Threading to delegate Message-ID generation to Message_Id_Util (added in #31) so the format matches the canonical NestJS reference and inbound Reply-To verification has something to check against.

Format change

Before After
Anchor ticket-{pk}-{reference}@{domain} (without angle brackets) <ticket-{id}@{domain}>
Reply reply-{id}-ticket-{reference}@{domain} <ticket-{id}-reply-{reply_id}@{domain}>

This is a breaking format change. Existing emails already sent stay as-is; new outbound mail uses the util's format, which is what inbound adapters will parse to route replies.

Signed Reply-To

add_threading_headers() now appends Reply-To: reply+{id}.{hmac8}@{domain} to the outbound headers when an inbound secret is configured. Inbound provider webhooks verify the 8-char HMAC-SHA256 prefix before routing replies.

Config

New WP options (preferred over constants):

  • escalated_email_domain — right-hand side of Message-IDs
  • escalated_email_inbound_secret — HMAC key (empty → Reply-To skipped)

Fallback order: option → ESCALATED_EMAIL_DOMAIN/_SECRET PHP constants → site_url() host → localhost.

Dependencies

Test plan

  • Existing test_generate_ticket_message_id / test_generate_reply_message_id / test_add_threading_headers_for_ticket assertions updated for the new format
  • 4 new tests for the signed Reply-To path (blank / configured, via direct helper call and via add_threading_headers integration)
  • CI green (lint + test (8.1) + test (8.2))

…ply-To

Refactors Email_Threading to delegate Message-ID generation to
Message_Id_Util (added in #31) so the format matches the canonical
NestJS reference and inbound Reply-To verification has something to
check against.

Format change:
  before:  reply-{id}-ticket-{reference}@{domain}
  after:   <ticket-{id}@{domain}>                       (anchor)
           <ticket-{id}-reply-{reply_id}@{domain}>      (reply)

Adds signed Reply-To to outbound mail headers when an inbound secret
is configured. The HMAC prefix on the local part means inbound
provider webhooks can route replies by ticket id without trusting
the mail client's threading headers.

New WP options (preferred over constants):
  escalated_email_domain         — right-hand side of Message-IDs
  escalated_email_inbound_secret — HMAC key (empty → Reply-To skipped)

Falls back to ESCALATED_EMAIL_DOMAIN / ESCALATED_EMAIL_INBOUND_SECRET
PHP constants, then site_url() host, then 'localhost'.

Updates existing test_generate_ticket_message_id /
test_generate_reply_message_id assertions for the new format. Adds
4 new tests for the signed Reply-To path (blank / configured, via
direct helper call and via add_threading_headers integration).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant