Skip to content

feat(inbound): InboundEmailService orchestrates reply/create + wire into controller#29

Open
mpge wants to merge 1 commit intofeat/mailgun-inbound-parserfrom
feat/inbound-email-orchestration
Open

feat(inbound): InboundEmailService orchestrates reply/create + wire into controller#29
mpge wants to merge 1 commit intofeat/mailgun-inbound-parserfrom
feat/inbound-email-orchestration

Conversation

@mpge
Copy link
Copy Markdown
Member

@mpge mpge commented Apr 24, 2026

Summary

Closes the last missing piece of the inbound webhook loop for Spring. Mirrors the .NET reference (escalated-dotnet #26).

Flow (per inbound POST)

parser → InboundMessage
  → InboundEmailService.process(message)
    → InboundEmailRouter.resolveTicket
      - ticket found  → TicketService.addReply(...) → REPLIED_TO_EXISTING
      - none + noise  → SKIPPED (SNS confirmation, empty body+subject)
      - none + content→ TicketService.create(subject, body,
                                              guestName, guestEmail, MEDIUM)
                         → CREATED_NEW

ProcessResult

Java record carrying:

  • outcome (REPLIED_TO_EXISTING | CREATED_NEW | SKIPPED)
  • ticketId / replyId
  • pendingAttachmentDownloads — provider-hosted attachment URLs (Mailgun) that a follow-up worker fetches + persists out-of-band. Empty when all attachments came inline (Postmark).

Controller response

Expanded from { status, ticketId } to:

{
  "outcome": "replied_to_existing",
  "ticketId": 42,
  "replyId": 99,
  "pendingAttachmentDownloads": [...]
}

Dependencies

Test plan

…nto controller

Closes the last missing piece of the inbound webhook loop for
Spring. Mirrors the .NET reference (#26).

Flow (per inbound POST):
  parser → InboundMessage
  → InboundEmailService.process(message)
    → InboundEmailRouter.resolveTicket
      - ticket found  → TicketService.addReply(...) → REPLIED_TO_EXISTING
      - none + noise  → SKIPPED (no-reply@sns.amazonaws.com,
                                  empty body+subject)
      - none + content→ TicketService.create(subject, body,
                                              guestName, guestEmail, MEDIUM)
                         → CREATED_NEW

ProcessResult carries:
  - outcome (REPLIED_TO_EXISTING | CREATED_NEW | SKIPPED)
  - ticketId / replyId
  - pendingAttachmentDownloads — provider-hosted attachment URLs
    (Mailgun) that a follow-up worker fetches + persists
    out-of-band. Empty when all attachments came inline (Postmark).

Controller response expanded from { status, ticketId } to:
  { outcome, ticketId, replyId, pendingAttachmentDownloads }

No tests yet in this commit — they require @SpringBootTest setup
with a TicketService fake; follow-up PR will add them matching the
.NET xUnit coverage.
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