@@ -76,9 +76,6 @@ func (e *Email) processMailbox(ctx context.Context, scanInboxSince time.Duration
76
76
case "none" :
77
77
client , err = imapclient .DialInsecure (address , imapOptions )
78
78
case "starttls" :
79
- fmt .Println ("starttls" )
80
- fmt .Println ("skip verify" , cfg .TLSSkipVerify )
81
- fmt .Println (address )
82
79
client , err = imapclient .DialStartTLS (address , imapOptions )
83
80
case "tls" :
84
81
client , err = imapclient .DialTLS (address , imapOptions )
@@ -132,13 +129,21 @@ func (e *Email) fetchAndProcessMessages(ctx context.Context, client *imapclient.
132
129
seqSet := imap.SeqSet {}
133
130
seqSet .AddRange (searchResults .Min , searchResults .Max )
134
131
135
- // Fetch only envelope, body is fetch later if the message is new .
132
+ // Fetch envelope and headers needed for auto-reply detection .
136
133
fetchOptions := & imap.FetchOptions {
137
134
Envelope : true ,
135
+ BodySection : []* imap.FetchItemBodySection {
136
+ {
137
+ Specifier : imap .PartSpecifierHeader ,
138
+ HeaderFields : []string {
139
+ "Auto-Submitted" ,
140
+ "X-Autoreply" ,
141
+ },
142
+ },
143
+ },
138
144
}
139
145
140
146
fetchCmd := client .Fetch (seqSet , fetchOptions )
141
-
142
147
for {
143
148
// Check for context cancellation before fetching the next message.
144
149
select {
@@ -154,7 +159,12 @@ func (e *Email) fetchAndProcessMessages(ctx context.Context, client *imapclient.
154
159
return nil
155
160
}
156
161
157
- // Process message envelope.
162
+ var (
163
+ env * imap.Envelope
164
+ autoReply bool
165
+ )
166
+
167
+ // Process all fetch items for the current message.
158
168
for {
159
169
// Check for context cancellation before processing the next item.
160
170
select {
@@ -164,32 +174,57 @@ func (e *Email) fetchAndProcessMessages(ctx context.Context, client *imapclient.
164
174
}
165
175
166
176
// Fetch the next item in the message.
167
- fetchItem := msg .Next ()
168
- if fetchItem == nil {
177
+ item := msg .Next ()
178
+ if item == nil {
169
179
// No message items left to process.
170
180
break
171
181
}
172
182
173
- // Process the envelope item.
174
- if item , ok := fetchItem .(imapclient.FetchItemDataEnvelope ); ok {
175
- if err := e .processEnvelope (ctx , client , item .Envelope , msg .SeqNum , inboxID ); err != nil && err != context .Canceled {
176
- e .lo .Error ("error processing envelope" , "error" , err )
183
+ // Body section.
184
+ if bs , ok := item .(imapclient.FetchItemDataBodySection ); ok && bs .Literal != nil {
185
+ envelope , err := enmime .ReadEnvelope (bs .Literal )
186
+ if err != nil {
187
+ e .lo .Error ("error reading envelope" , "error" , err )
188
+ continue
189
+ }
190
+ if isAutoReply (envelope ) {
191
+ autoReply = true
177
192
}
178
193
}
194
+
195
+ // Envelope.
196
+ if ed , ok := item .(imapclient.FetchItemDataEnvelope ); ok {
197
+ env = ed .Envelope
198
+ }
199
+ }
200
+
201
+ // Skip if we couldn't get headers or envelope.
202
+ if env == nil {
203
+ continue
204
+ }
205
+
206
+ // Skip if this is an auto-reply message.
207
+ if autoReply {
208
+ e .lo .Info ("skipping auto-reply message" , "subject" , env .Subject , "message_id" , env .MessageID )
209
+ continue
210
+ }
211
+
212
+ // Process the envelope.
213
+ if err := e .processEnvelope (ctx , client , env , msg .SeqNum , inboxID ); err != nil && err != context .Canceled {
214
+ e .lo .Error ("error processing envelope" , "error" , err )
179
215
}
180
216
}
181
217
}
182
218
183
- // processEnvelope processes an email envelope.
219
+ // processEnvelope processes a single email envelope.
184
220
func (e * Email ) processEnvelope (ctx context.Context , client * imapclient.Client , env * imap.Envelope , seqNum uint32 , inboxID int ) error {
185
221
if len (env .From ) == 0 {
186
222
e .lo .Warn ("no sender received for email" , "message_id" , env .MessageID )
187
223
return nil
188
224
}
189
225
var fromAddress = strings .ToLower (env .From [0 ].Addr ())
190
226
191
- // Check if the message already exists in the database.
192
- // If it does, ignore it.
227
+ // Check if the message already exists in the database; if it does, ignore it.
193
228
exists , err := e .messageStore .MessageExists (env .MessageID )
194
229
if err != nil {
195
230
e .lo .Error ("error checking if message exists" , "message_id" , env .MessageID )
@@ -211,7 +246,7 @@ func (e *Email) processEnvelope(ctx context.Context, client *imapclient.Client,
211
246
return nil
212
247
}
213
248
214
- e .lo .Debug ("message does not exist " , "message_id" , env .MessageID )
249
+ e .lo .Debug ("processing new incoming message " , "message_id" , env .MessageID , "subject" , env . Subject , "from" , fromAddress , "inbox_id" , inboxID )
215
250
216
251
// Make contact.
217
252
firstName , lastName := getContactName (env .From [0 ])
@@ -277,6 +312,7 @@ func (e *Email) processEnvelope(ctx context.Context, client *imapclient.Client,
277
312
InboxID : inboxID ,
278
313
}
279
314
315
+ // Fetch full message body.
280
316
fetchOptions := & imap.FetchOptions {
281
317
BodySection : []* imap.FetchItemBodySection {{}},
282
318
}
@@ -310,24 +346,7 @@ func (e *Email) processEnvelope(ctx context.Context, client *imapclient.Client,
310
346
}
311
347
}
312
348
313
- // extractAllHTMLParts extracts all HTML parts from the given enmime part by traversing the tree.
314
- func extractAllHTMLParts (part * enmime.Part ) []string {
315
- var htmlParts []string
316
-
317
- // Check current part
318
- if strings .HasPrefix (part .ContentType , "text/html" ) && len (part .Content ) > 0 {
319
- htmlParts = append (htmlParts , string (part .Content ))
320
- }
321
-
322
- // Process children recursively
323
- for child := part .FirstChild ; child != nil ; child = child .NextSibling {
324
- childParts := extractAllHTMLParts (child )
325
- htmlParts = append (htmlParts , childParts ... )
326
- }
327
-
328
- return htmlParts
329
- }
330
-
349
+ // processFullMessage processes the full message and enqueues it for inserting into the database.
331
350
func (e * Email ) processFullMessage (item imapclient.FetchItemDataBodySection , incomingMsg models.IncomingMessage ) error {
332
351
envelope , err := enmime .ReadEnvelope (item .Literal )
333
352
if err != nil {
@@ -432,3 +451,32 @@ func getContactName(imapAddr imap.Address) (string, string) {
432
451
}
433
452
return names [0 ], names [1 ]
434
453
}
454
+
455
+ // isAutoReply checks if a given email envelope indicates an auto-reply message.
456
+ func isAutoReply (envelope * enmime.Envelope ) bool {
457
+ if as := strings .ToLower (strings .TrimSpace (envelope .GetHeader ("Auto-Submitted" ))); as != "" && as != "no" {
458
+ return true
459
+ }
460
+ if strings .TrimSpace (envelope .GetHeader ("X-Autoreply" )) != "" {
461
+ return true
462
+ }
463
+ return false
464
+ }
465
+
466
+ // extractAllHTMLParts extracts all HTML parts from the given enmime part by traversing the tree.
467
+ func extractAllHTMLParts (part * enmime.Part ) []string {
468
+ var htmlParts []string
469
+
470
+ // Check current part
471
+ if strings .HasPrefix (part .ContentType , "text/html" ) && len (part .Content ) > 0 {
472
+ htmlParts = append (htmlParts , string (part .Content ))
473
+ }
474
+
475
+ // Process children recursively
476
+ for child := part .FirstChild ; child != nil ; child = child .NextSibling {
477
+ childParts := extractAllHTMLParts (child )
478
+ htmlParts = append (htmlParts , childParts ... )
479
+ }
480
+
481
+ return htmlParts
482
+ }
0 commit comments