Skip to content

feat(email): wire MessageIdUtil into EmailTemplates + signed Reply-To#22

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

feat(email): wire MessageIdUtil into EmailTemplates + signed Reply-To#22
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 EmailTemplates.NewTicket / TicketReply / SlaBreachAlert to delegate Message-ID generation to MessageIdUtil (added in #21) so the format matches the canonical NestJS reference, and adds a signed Reply-To header so inbound provider webhooks can verify ticket identity.

Format change

Before After
Anchor <{ticket.Reference}@escalated> <ticket-{id}@{domain}>
Reply <{ticket.Reference}-reply-{reply.Id}@escalated> <ticket-{id}-reply-{replyId}@{domain}>
SLA <{ticket.Reference}-sla-{breachType}@escalated> In-Reply-To + References point at ticket root

The hardcoded @escalated pseudo-domain is replaced with the new EmailOptions.Domain config value. Every template now takes an EmailOptions parameter so the domain + secret don't have to be baked into call sites.

Config

EscalatedOptions.Email.Domain        // default: "localhost"
EscalatedOptions.Email.InboundSecret // default: "" — Reply-To skipped

Host apps with the default DI wiring resolve IOptions<EscalatedOptions> and pass options.Value.Email to the templates.

EmailMessage.ReplyTo

New nullable ReplyTo property on EmailMessage holds the signed reply+{id}.{hmac8}@{domain} address (or null when InboundSecret is blank). Inbound provider webhooks verify via MessageIdUtil.VerifyReplyTo to route replies back to their ticket even when clients strip the Message-ID chain.

Dependencies

Test plan

  • 6 xUnit tests cover the Message-ID format, In-Reply-To / References headers, signed Reply-To presence / absence, and the default-option values
  • CI green (won't trigger against stacked base until rebased)

…y-To

Refactors EmailTemplates (NewTicket / TicketReply / SlaBreachAlert)
to delegate Message-ID generation to MessageIdUtil (added in #21) so
the format matches the canonical NestJS reference:

  before:  <{ticket.Reference}@escalated>
           <{ticket.Reference}-reply-{reply.Id}@escalated>
           <{ticket.Reference}-sla-{breachType}@escalated>
  after:   <ticket-{id}@{domain}>
           <ticket-{id}-reply-{replyId}@{domain}>

The hardcoded '@escalated' pseudo-domain is replaced with the new
EmailOptions.Domain config value. Every template now takes an
EmailOptions parameter so the domain + secret don't have to be
baked into call sites.

Adds EmailOptions config:
  EscalatedOptions.Email.Domain        (default: 'localhost')
  EscalatedOptions.Email.InboundSecret (default: empty — Reply-To skipped)

Adds EmailMessage.ReplyTo for the signed Reply-To address
(reply+{id}.{hmac8}@{domain}) which inbound provider webhooks verify
via MessageIdUtil.VerifyReplyTo to route inbound mail back to its
ticket even when clients strip the Message-ID / In-Reply-To chain.

6 new xUnit tests cover the format, thread headers, signed Reply-To
presence/absence, and the default-option values.
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