mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-21 16:03:12 +00:00
feat: fetch the warmup verify token and machine-reply headers via BODY.PEEK on IMAP sync and surface them as flag pseudo-headers, and join replies to the parent's thread instead of forking a new thread at every reply depth
This commit is contained in:
@@ -36,7 +36,16 @@ func (w *WMail) onImapEmailUpdate(ctx context.Context, msg *models.EmailMessageD
|
||||
if parentID != "" {
|
||||
internalParent, _ := w.EmailMessageMapRepository.Get(ctx, w.UserID, w.ID, parentID)
|
||||
if internalParent != nil {
|
||||
threadID = internalParent.ID
|
||||
// Join the parent's thread; using the parent's internal id here
|
||||
// forked a new thread at every reply depth.
|
||||
threadID = internalParent.ThreadID
|
||||
if threadID == "" {
|
||||
threadID = parentID
|
||||
}
|
||||
} else {
|
||||
// Parent unknown (e.g. reply to a pre-connect message): root a
|
||||
// thread on the parent's RFC id so siblings still group.
|
||||
threadID = parentID
|
||||
}
|
||||
} else {
|
||||
threadID = msg.MessageID
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
@@ -19,6 +23,13 @@ import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// headerFetchFields are the internet headers fetched alongside each changed
|
||||
// message (BODY.PEEK[HEADER.FIELDS ...]) and surfaced into Flags as
|
||||
// "Header:value" pseudo-flags: the warmup verification token plus the
|
||||
// machine-reply/DSN markers the consumer's reply/bounce classifier reads.
|
||||
// The IMAP ENVELOPE carries none of these.
|
||||
var headerFetchFields = append([]string{config.WarmupVerifyHeader}, config.InboundClassificationHeaders...)
|
||||
|
||||
type Client struct {
|
||||
Email string
|
||||
AuthType models.AuthType
|
||||
@@ -166,12 +177,20 @@ func (c *Client) FetchChanges(ctx context.Context, lastModSeq uint64) *errx.Mail
|
||||
ModSeq: true,
|
||||
InternalDate: true,
|
||||
ChangedSince: lastModSeq,
|
||||
BodySection: []*imap.FetchItemBodySection{{
|
||||
Specifier: imap.PartSpecifierHeader,
|
||||
HeaderFields: headerFetchFields,
|
||||
Peek: true,
|
||||
}},
|
||||
})
|
||||
for em := cmd.Next(); em != nil; em = cmd.Next() {
|
||||
var email models.EmailMessageData
|
||||
var euid imap.UID
|
||||
|
||||
var bodyStructure imap.BodyStructure
|
||||
// Collected separately: the FLAGS item resets email.Flags and item
|
||||
// order is server-dependent, so appending inline could be wiped.
|
||||
var headerFlags []string
|
||||
|
||||
for item := em.Next(); item != nil; item = em.Next() {
|
||||
switch item := item.(type) {
|
||||
@@ -202,9 +221,12 @@ func (c *Client) FetchChanges(ctx context.Context, lastModSeq uint64) *errx.Mail
|
||||
email.ModSeq = item.ModSeq
|
||||
case imapclient.FetchItemDataBodyStructure:
|
||||
bodyStructure = item.BodyStructure
|
||||
case imapclient.FetchItemDataBodySection:
|
||||
headerFlags = parseHeaderFlags(item.Literal)
|
||||
}
|
||||
}
|
||||
|
||||
email.Flags = append(email.Flags, headerFlags...)
|
||||
email.BodyPlain, email.BodyHTML = fetchTextParts(c.client, euid, bodyStructure)
|
||||
|
||||
if err := c.OnUpdate(ctx, &email); err != nil {
|
||||
@@ -219,3 +241,24 @@ func (c *Client) FetchChanges(ctx context.Context, lastModSeq uint64) *errx.Mail
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseHeaderFlags reads a HEADER.FIELDS literal and renders the fetched
|
||||
// headers as "Header:value" pseudo-flags, using the canonical names from
|
||||
// headerFetchFields so the consumer's prefix matching always hits.
|
||||
func parseHeaderFlags(lit io.Reader) []string {
|
||||
if lit == nil {
|
||||
return nil
|
||||
}
|
||||
tp := textproto.NewReader(bufio.NewReader(io.LimitReader(lit, 32*1024)))
|
||||
hdr, err := tp.ReadMIMEHeader()
|
||||
if len(hdr) == 0 && err != nil {
|
||||
return nil
|
||||
}
|
||||
var out []string
|
||||
for _, name := range headerFetchFields {
|
||||
if v := strings.TrimSpace(hdr.Get(name)); v != "" {
|
||||
out = append(out, name+":"+v)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user