Subsystem to monitor IMAP mailbox for new messages. Introduces: - intake: worker that uses IDLE or polling to detect new emails. - imap: client wrapper for connection management and IMAP commands. - filter: logic for IMAP search and sender allow-list. - tracker: concurrency control to prevent processing the same UID twice. - backoff: for handling connection retries with jitter.
53 lines
1.8 KiB
Go
53 lines
1.8 KiB
Go
// Package filter provides message filtering configuration.
|
|
// It is used to restrict which messages are processed by the answer workers.
|
|
//
|
|
// NOTE: IMAP Search Limitations
|
|
// IMAP SEARCH (RFC 3501 §6.4.4) uses case-insensitive substring matching for
|
|
// text fields.
|
|
// A filter like From: "alice@example.com" would match:
|
|
// - alice@example.com
|
|
// - malice@example.com
|
|
// - alice@example.com.evil.org
|
|
//
|
|
// For this reason, server-side IMAP filters should be considered
|
|
// performance optimizations only, not security controls. Use the
|
|
// [Filters.Senders] allowlist for exact sender matching.
|
|
//
|
|
// [RFC 3501 §6.4.4]:
|
|
// https://datatracker.ietf.org/doc/html/rfc3501#section-6.4.4
|
|
package filter
|
|
|
|
import "strings"
|
|
|
|
// Filters holds message filtering configuration for IMAP searches
|
|
// and sender verification.
|
|
type Filters struct {
|
|
// Body contains keywords that must appear in the message body.
|
|
Body []string `yaml:"body"`
|
|
// From filters messages by the From header field.
|
|
From string `yaml:"from"`
|
|
// Senders is an allowlist of email addresses permitted to receive
|
|
// replies. If empty, all senders are allowed.
|
|
Senders []string `yaml:"allowed_senders"`
|
|
// Subject filters messages by the Subject header field.
|
|
Subject string `yaml:"subject"`
|
|
// To filters messages by the To header field.
|
|
To string `yaml:"to"`
|
|
}
|
|
|
|
// MatchSender checks if the sender is in the allowed list.
|
|
// Returns true if the allowlist is empty (allow all) or if the sender matches.
|
|
// Comparison is case-insensitive and ignores leading/trailing whitespace.
|
|
func (f *Filters) MatchSender(sender string) bool {
|
|
if len(f.Senders) == 0 {
|
|
return true
|
|
}
|
|
sender = strings.ToLower(strings.TrimSpace(sender))
|
|
for _, allowed := range f.Senders {
|
|
if strings.ToLower(strings.TrimSpace(allowed)) == sender {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|