@@ -20,8 +20,10 @@ import {
2020 GOOGLE_SEND_SCOPE ,
2121} from "@/lib/provider-scopes" ;
2222import {
23+ extractMessageReferenceIds ,
2324 extractEmailAddress ,
2425 matchThreadedInboundReplies ,
26+ normalizeMessageReferenceId ,
2527} from "@/lib/reply-detection" ;
2628import { decryptSecret , encryptSecret } from "@/lib/secrets" ;
2729import { recordReplySignal } from "@/lib/sequences" ;
@@ -88,6 +90,12 @@ type GmailSendDiagnosticMessage = {
8890 id ?: string ;
8991 internalDate ?: string ;
9092 labelIds ?: string [ ] ;
93+ payload ?: {
94+ headers ?: Array < {
95+ name ?: string ;
96+ value ?: string ;
97+ } > ;
98+ } ;
9199 threadId ?: string ;
92100} ;
93101
@@ -186,6 +194,23 @@ function getGoogleReplySummary(message: GmailMetadataMessage) {
186194 return subject ? `Automatic Gmail reply detected: ${ subject } ` : null ;
187195}
188196
197+ function getGoogleHeaderValue (
198+ message : GmailMetadataMessage | GmailSendDiagnosticMessage | null | undefined ,
199+ name : string ,
200+ ) {
201+ return (
202+ message ?. payload ?. headers ?. find (
203+ ( header ) => header . name ?. toLowerCase ( ) === name . toLowerCase ( ) ,
204+ ) ?. value ?? null
205+ ) ;
206+ }
207+
208+ function getGoogleProviderInternetMessageId (
209+ message : GmailSendDiagnosticMessage | null ,
210+ ) {
211+ return normalizeMessageReferenceId ( getGoogleHeaderValue ( message , "Message-ID" ) ) ;
212+ }
213+
189214async function ensureIdentity ( params : {
190215 contactId : string ;
191216 kind : "email" | "provider_contact_id" | "provider_person_id" ;
@@ -532,28 +557,40 @@ async function listRecentGoogleInboundMessages(input: {
532557 ) ;
533558
534559 const results = await Promise . all (
535- ( listResponse . messages ?? [ ] ) . map ( ( message ) =>
536- googleFetch < GmailMetadataMessage > (
537- `https://gmail.googleapis.com/gmail/v1/users/me/messages/ ${ message . id } ? ${ new URLSearchParams ( {
560+ ( listResponse . messages ?? [ ] ) . map ( ( message ) => {
561+ const metadataParams = ( ( ) => {
562+ const params = new URLSearchParams ( {
538563 format : "metadata" ,
539- metadataHeaders : "From" ,
540- } ) . toString ( ) } &metadataHeaders=Subject`,
564+ } ) ;
565+ params . append ( "metadataHeaders" , "From" ) ;
566+ params . append ( "metadataHeaders" , "Subject" ) ;
567+ params . append ( "metadataHeaders" , "In-Reply-To" ) ;
568+ params . append ( "metadataHeaders" , "References" ) ;
569+ return params ;
570+ } ) ( ) ;
571+
572+ return googleFetch < GmailMetadataMessage > (
573+ `https://gmail.googleapis.com/gmail/v1/users/me/messages/${ message . id } ?${ metadataParams . toString ( ) } ` ,
541574 {
542575 headers : {
543576 Authorization : `Bearer ${ input . accessToken } ` ,
544577 } ,
545578 } ,
546579 "Unable to fetch Gmail reply metadata" ,
547- ) ,
548- ) ,
580+ ) ;
581+ } ) ,
549582 ) ;
550583
551584 return results
552585 . map ( ( message ) => ( {
586+ inReplyTo : getGoogleHeaderValue ( message , "In-Reply-To" ) ,
553587 providerThreadId : message . threadId ?? null ,
588+ referenceMessageIds : extractMessageReferenceIds (
589+ getGoogleHeaderValue ( message , "References" ) ,
590+ ) ,
554591 receivedAt : new Date ( Number ( message . internalDate ?? Date . now ( ) ) ) ,
555592 senderEmail : extractEmailAddress (
556- message . payload ?. headers ?. find ( ( header ) => header . name === "From" ) ?. value ,
593+ getGoogleHeaderValue ( message , "From" ) ,
557594 ) ,
558595 summary : getGoogleReplySummary ( message ) ,
559596 } ) )
@@ -622,6 +659,17 @@ export async function syncGoogleRepliesForAccount(accountId: string) {
622659 {
623660 contactId : string ;
624661 contactName : string ;
662+ providerInternetMessageId ?: string | null ;
663+ senderEmail : string ;
664+ sentAt : Date ;
665+ }
666+ > ( ) ;
667+ const sentMessageReferences = new Map <
668+ string ,
669+ {
670+ contactId : string ;
671+ contactName : string ;
672+ providerInternetMessageId ?: string | null ;
625673 senderEmail : string ;
626674 sentAt : Date ;
627675 }
@@ -631,6 +679,12 @@ export async function syncGoogleRepliesForAccount(accountId: string) {
631679 const providerThreadId = message . providerThreadId ?. trim ( ) ;
632680 const contact = contactMap . get ( message . contactId ) ;
633681 const senderEmail = normalizeEmail ( contact ?. primaryEmail ) ;
682+ const providerInternetMessageId = normalizeMessageReferenceId (
683+ typeof ( message . metadata as Record < string , unknown > | null ) ?. providerInternetMessageId ===
684+ "string"
685+ ? ( ( message . metadata as Record < string , unknown > ) . providerInternetMessageId as string )
686+ : null ,
687+ ) ;
634688
635689 if ( ! providerThreadId || ! senderEmail || ! message . sentAt ) {
636690 continue ;
@@ -642,6 +696,17 @@ export async function syncGoogleRepliesForAccount(accountId: string) {
642696 sentThreads . set ( providerThreadId , {
643697 contactId : message . contactId ,
644698 contactName : contact ?. displayName ?? "Contact" ,
699+ providerInternetMessageId,
700+ senderEmail,
701+ sentAt : message . sentAt ,
702+ } ) ;
703+ }
704+
705+ if ( providerInternetMessageId ) {
706+ sentMessageReferences . set ( providerInternetMessageId , {
707+ contactId : message . contactId ,
708+ contactName : contact ?. displayName ?? "Contact" ,
709+ providerInternetMessageId,
645710 senderEmail,
646711 sentAt : message . sentAt ,
647712 } ) ;
@@ -651,6 +716,7 @@ export async function syncGoogleRepliesForAccount(accountId: string) {
651716 const replyMatches = matchThreadedInboundReplies (
652717 sentThreads ,
653718 recentInboundMessages ,
719+ sentMessageReferences ,
654720 ) ;
655721
656722 let detectedCount = 0 ;
@@ -718,7 +784,6 @@ export async function syncGoogleContactsForAccount(accountId: string) {
718784 let nextSyncToken = account . syncCursor ?? undefined ;
719785 let syncedCount = 0 ;
720786
721- try {
722787 try {
723788 do {
724789 const params = new URLSearchParams ( {
@@ -901,24 +966,6 @@ export async function syncGoogleContactsForAccount(accountId: string) {
901966
902967 throw error ;
903968 }
904- } catch ( error ) {
905- const message = error instanceof Error ? error . message : "Google sync failed." ;
906-
907- if ( message . includes ( "(410)" ) && account . syncCursor ) {
908- await db
909- . update ( connectedAccounts )
910- . set ( {
911- syncCursor : null ,
912- updatedAt : new Date ( ) ,
913- } )
914- . where ( eq ( connectedAccounts . id , account . id ) ) ;
915-
916- return syncGoogleContactsForAccount ( accountId ) ;
917- }
918-
919- throw error ;
920- }
921-
922969 await db
923970 . update ( connectedAccounts )
924971 . set ( {
@@ -984,7 +1031,13 @@ export async function sendGoogleMessage(input: {
9841031
9851032 const messageMetadata = response ?. id
9861033 ? await googleFetch < GmailSendDiagnosticMessage > (
987- `https://gmail.googleapis.com/gmail/v1/users/me/messages/${ response . id } ?format=minimal` ,
1034+ ( ( ) => {
1035+ const params = new URLSearchParams ( {
1036+ format : "metadata" ,
1037+ } ) ;
1038+ params . append ( "metadataHeaders" , "Message-ID" ) ;
1039+ return `https://gmail.googleapis.com/gmail/v1/users/me/messages/${ response . id } ?${ params . toString ( ) } ` ;
1040+ } ) ( ) ,
9881041 {
9891042 headers : {
9901043 Authorization : `Bearer ${ accessToken } ` ,
@@ -998,6 +1051,7 @@ export async function sendGoogleMessage(input: {
9981051 diagnostic : messageMetadata ?. id
9991052 ? `gmail message ${ messageMetadata . id } in thread ${ messageMetadata . threadId ?? "unknown" } `
10001053 : `gmail send completed for outbound ${ input . messageId } ` ,
1054+ providerInternetMessageId : getGoogleProviderInternetMessageId ( messageMetadata ) ,
10011055 providerMessageId : response ?. id ?? null ,
10021056 providerThreadId : messageMetadata ?. threadId ?? response ?. threadId ?? null ,
10031057 } ;
0 commit comments