fix(chat): fetch inbound email body from the Received Emails API (#320)#325
Open
iPLAYCAFE-dev wants to merge 1 commit into
Open
fix(chat): fetch inbound email body from the Received Emails API (#320)#325iPLAYCAFE-dev wants to merge 1 commit into
iPLAYCAFE-dev wants to merge 1 commit into
Conversation
…ckbackIO#320) Resend's `email.received` webhook payload is metadata-only — it carries no text/html body. ingestInboundEmail read `data.text` inline, so every inbound reply routed to a conversation was stored-body-less, stripped to '', and dropped as `empty` (200-acked, never retried): email reply-threading silently never worked on the Resend inbound channel. Fix: when the parsed payload has no text but carries the provider email id, fetch the body via the Received Emails API (`resend.emails.receiving.get`, already in the pinned SDK) before extractReplyText. Design points: - Fetch AFTER routing + dedupe, so unroutable/duplicate deliveries never cost an API call. - `not_found` -> null -> the existing `empty` drop; any other API error throws, the route 500s, and Resend redelivers (idempotent via the Message-ID dedupe). - html-only bodies fall back through a small htmlToText (block tags -> newlines, entities unescaped, per-line leading whitespace stripped so extractReplyText's ^-anchored quote separators still match). - parseInboundEmail also picks up `message_id` for dedupe precedence and exposes `email_id` as `emailId`. Verified end-to-end on a production install: a real external mail to a minted reply+<id>.<sig> address threaded into its conversation with the fetched body as content; without the fix the same delivery ends status:'empty'. Fixes QuackbackIO#320
d0df7e3 to
74606de
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #320
Problem
Resend's
email.receivedwebhook payload is metadata-only — it carries notext/htmlbody.ingestInboundEmailreaddata.textinline, so every inbound reply that routed to a conversation arrived body-less, was stripped to'', and dropped asempty(200-acked, so Resend never retried). Net effect: email reply-threading silently never works on the Resend inbound channel.Fix
When the parsed payload has no text but carries the provider email id, fetch the body via the Received Emails API (
resend.emails.receiving.get— already available in the pinnedresendSDK) beforeextractReplyText:not_found→null→ the existingemptydrop; any other API error throws → the route 500s → Resend redelivers (idempotent via the Message-ID dedupe + the partial unique index).htmlToText(block tags → newlines, entities unescaped, per-line leading whitespace stripped soextractReplyText's^-anchored quote separators still match).parseInboundEmailadditionally prefersmessage_idfor dedupe and exposesemail_idasemailId.No config, schema, or API-surface changes; when the payload does carry inline text (e.g. other providers, tests), behavior is unchanged and no fetch happens.
Testing
not_found→empty, transient failure propagates for redelivery,emailId/message_idparsing precedence,htmlToTextbehavior (incl. feedingextractReplyText), and the no-API-key →nullguard in@quackback/email.chat-email-inbound,chat-email-ingest,email-webhook-handler,chat-email-channel,packages/email): 70/70.reply+<id>.<sig>@<inbound-domain>address traversed MX → Resend → webhook → Svix verify → ingest and threaded into its conversation with the fetched body as the message content. Without the patch the identical delivery endsstatus:"empty".Notes
@quackback/email(getReceivedEmail) so the chat domain stays transport-agnostic.from_mismatch/rate_limiteddrop still costs one fetch (those checks need the visitor row anyway); moving them earlier felt like a bigger refactor than Bug: Inbound replies dropped on Resend: code reads body from metadata-only webhook #320 warrants — happy to adjust if you'd prefer.