Skip to content

Commit 53d5715

Browse files
committed
fix: implement loop prevention header in emails
#Ref 90
1 parent b561e79 commit 53d5715

File tree

2 files changed

+49
-7
lines changed

2 files changed

+49
-7
lines changed

internal/inbox/channel/email/imap.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/abhinavxd/libredesk/internal/attachment"
1212
"github.com/abhinavxd/libredesk/internal/conversation/models"
1313
"github.com/abhinavxd/libredesk/internal/envelope"
14+
"github.com/abhinavxd/libredesk/internal/stringutil"
1415
umodels "github.com/abhinavxd/libredesk/internal/user/models"
1516
"github.com/emersion/go-imap/v2"
1617
"github.com/emersion/go-imap/v2/imapclient"
@@ -136,8 +137,9 @@ func (e *Email) fetchAndProcessMessages(ctx context.Context, client *imapclient.
136137
{
137138
Specifier: imap.PartSpecifierHeader,
138139
HeaderFields: []string{
139-
"Auto-Submitted",
140-
"X-Autoreply",
140+
headerAutoSubmitted,
141+
headerAutoreply,
142+
headerLibredeskLoopPrevention,
141143
},
142144
},
143145
},
@@ -148,10 +150,18 @@ func (e *Email) fetchAndProcessMessages(ctx context.Context, client *imapclient.
148150
env *imap.Envelope
149151
seqNum uint32
150152
autoReply bool
153+
isLoop bool
151154
}
152155
var messages []msgData
153156

154157
fetchCmd := client.Fetch(seqSet, fetchOptions)
158+
159+
// Extract the inbox email address.
160+
inboxEmail, err := stringutil.ExtractEmail(e.FromAddress())
161+
if err != nil {
162+
e.lo.Error("failed to extract email address from the 'From' header", "error", err)
163+
return fmt.Errorf("failed to extract email address from 'From' header: %w", err)
164+
}
155165
for {
156166
// Check for context cancellation before fetching the next message.
157167
select {
@@ -170,6 +180,7 @@ func (e *Email) fetchAndProcessMessages(ctx context.Context, client *imapclient.
170180
var (
171181
env *imap.Envelope
172182
autoReply bool
183+
isLoop bool
173184
)
174185
// Process all fetch items for the current message.
175186
for {
@@ -197,6 +208,9 @@ func (e *Email) fetchAndProcessMessages(ctx context.Context, client *imapclient.
197208
if isAutoReply(envelope) {
198209
autoReply = true
199210
}
211+
if isLoopMessage(envelope, inboxEmail) {
212+
isLoop = true
213+
}
200214
}
201215

202216
// Envelope.
@@ -210,7 +224,7 @@ func (e *Email) fetchAndProcessMessages(ctx context.Context, client *imapclient.
210224
continue
211225
}
212226

213-
messages = append(messages, msgData{env: env, seqNum: msg.SeqNum, autoReply: autoReply})
227+
messages = append(messages, msgData{env: env, seqNum: msg.SeqNum, autoReply: autoReply, isLoop: isLoop})
214228
}
215229

216230
// Now process each collected message.
@@ -228,6 +242,12 @@ func (e *Email) fetchAndProcessMessages(ctx context.Context, client *imapclient.
228242
continue
229243
}
230244

245+
// Skip if this message is a loop prevention message.
246+
if msgData.isLoop {
247+
e.lo.Info("skipping message with loop prevention header", "subject", msgData.env.Subject, "message_id", msgData.env.MessageID)
248+
continue
249+
}
250+
231251
// Process the envelope.
232252
if err := e.processEnvelope(ctx, client, msgData.env, msgData.seqNum, inboxID); err != nil && err != context.Canceled {
233253
e.lo.Error("error processing envelope", "error", err)
@@ -484,6 +504,15 @@ func isAutoReply(envelope *enmime.Envelope) bool {
484504
return false
485505
}
486506

507+
// isLoopMessage returns true if the email is a loop prevention message. i.e., it has the `X-Libredesk-Loop-Prevention` header with the inbox email address.
508+
func isLoopMessage(envelope *enmime.Envelope, inboxEmailaddress string) bool {
509+
loopHeader := envelope.GetHeader(headerLibredeskLoopPrevention)
510+
if loopHeader == "" {
511+
return false
512+
}
513+
return strings.EqualFold(loopHeader, inboxEmailaddress)
514+
}
515+
487516
// extractAllHTMLParts extracts all HTML parts from the given enmime part by traversing the tree.
488517
func extractAllHTMLParts(part *enmime.Part) []string {
489518
var htmlParts []string

internal/inbox/channel/email/smtp.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,19 @@ import (
88
"net/textproto"
99

1010
"github.com/abhinavxd/libredesk/internal/conversation/models"
11+
"github.com/abhinavxd/libredesk/internal/stringutil"
1112
"github.com/knadh/smtppool"
1213
)
1314

1415
const (
15-
headerReturnPath = "Return-Path"
16-
headerMessageID = "Message-ID"
17-
headerReferences = "References"
18-
headerInReplyTo = "In-Reply-To"
16+
headerReturnPath = "Return-Path"
17+
headerMessageID = "Message-ID"
18+
headerReferences = "References"
19+
headerInReplyTo = "In-Reply-To"
20+
headerLibredeskLoopPrevention = "X-Libredesk-Loop-Prevention"
21+
headerAutoreply = "X-Autoreply"
22+
headerAutoSubmitted = "Auto-Submitted"
23+
1924
dispositionInline = "inline"
2025
)
2126

@@ -102,6 +107,14 @@ func (e *Email) Send(m models.Message) error {
102107
Headers: textproto.MIMEHeader{},
103108
}
104109

110+
// Set libredesk loop prevention header to from address.
111+
emailAddress, err := stringutil.ExtractEmail(m.From)
112+
if err != nil {
113+
e.lo.Error("Failed to extract email address from the 'From' header", "error", err)
114+
return fmt.Errorf("failed to extract email address from 'From' header: %w", err)
115+
}
116+
email.Headers.Set(headerLibredeskLoopPrevention, emailAddress)
117+
105118
// Attach SMTP level headers
106119
for key, value := range e.headers {
107120
email.Headers.Set(key, value)

0 commit comments

Comments
 (0)