A search failure was logged but execution continued, silently returning 'no emails' to the caller. This masked the most likely cause of the polling not working. Error now propagates so the caller can log and handle it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
112 lines
2.4 KiB
Go
112 lines
2.4 KiB
Go
package email
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/mail"
|
|
"strings"
|
|
|
|
"github.com/emersion/go-imap"
|
|
"github.com/emersion/go-imap/client"
|
|
)
|
|
|
|
func FetchEmails() ([]Email, error) {
|
|
config, err := NewConfigFromEnv()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
imapClient, err := config.ConnectToServer(IMAP_SERVICE)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer imapClient.Logout()
|
|
|
|
return fetchEmails(imapClient)
|
|
}
|
|
|
|
func concatAddresses(addresses []*imap.Address) string {
|
|
result := []string{}
|
|
|
|
for _, address := range addresses {
|
|
if len(address.PersonalName) > 0 {
|
|
result = append(result, fmt.Sprintf("%s <%s>", address.PersonalName, address.Address()))
|
|
} else {
|
|
result = append(result, address.Address())
|
|
}
|
|
|
|
}
|
|
|
|
return strings.Join(result, "; ")
|
|
}
|
|
|
|
func fetchEmails(imapClient *client.Client) ([]Email, error) {
|
|
result := []Email{}
|
|
|
|
// Select the mailbox you want to read
|
|
_, err := imapClient.Select("INBOX", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
criteria := imap.NewSearchCriteria()
|
|
criteria.WithoutFlags = []string{imap.SeenFlag}
|
|
uids, err := imapClient.Search(criteria)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("search error: %w", err)
|
|
}
|
|
|
|
if len(uids) > 0 {
|
|
// Define the range of emails to fetch
|
|
seqSet := new(imap.SeqSet)
|
|
seqSet.AddNum(uids...)
|
|
|
|
// Fetch the required message attributes
|
|
messages := make(chan *imap.Message, 10)
|
|
fetchErr := make(chan error, 1)
|
|
section := &imap.BodySectionName{}
|
|
items := []imap.FetchItem{section.FetchItem(), imap.FetchEnvelope}
|
|
|
|
go func() {
|
|
fetchErr <- imapClient.Fetch(seqSet, items, messages)
|
|
}()
|
|
|
|
// Drain the channel on early return so the goroutine can always exit.
|
|
defer func() {
|
|
for range messages {
|
|
}
|
|
}()
|
|
|
|
for msg := range messages {
|
|
if len(msg.Envelope.From) == 0 {
|
|
continue
|
|
}
|
|
toAddress := concatAddresses(msg.Envelope.To)
|
|
fromAddress := msg.Envelope.From[0].Address()
|
|
r := msg.GetBody(section)
|
|
if r == nil {
|
|
return result, fmt.Errorf("server didn't returned message body")
|
|
}
|
|
m, err := mail.ReadMessage(r)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
body, err := io.ReadAll(m.Body)
|
|
|
|
email := NewEmail(
|
|
[]string{toAddress},
|
|
[]string{},
|
|
fromAddress,
|
|
msg.Envelope.Subject,
|
|
string(body),
|
|
)
|
|
result = append(result, *email)
|
|
}
|
|
|
|
if err := <-fetchErr; err != nil {
|
|
return result, fmt.Errorf("fetch error: %w", err)
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|