areweplaying/pkg/email/fetch.go
Harry Culpan c509641f59 Guard against empty From header in IMAP message fetch
Bounce messages and MAILER-DAEMON notices can have no From address,
causing a panic on the bare [0] index access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 18:29:05 -04:00

104 lines
2.2 KiB
Go

package email
import (
"fmt"
"io"
"log"
"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 {
log.Printf("Search error: %s\n", 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)
section := &imap.BodySectionName{}
items := []imap.FetchItem{section.FetchItem(), imap.FetchEnvelope}
go func() {
if err := imapClient.Fetch(seqSet, items, messages); err != nil {
log.Fatal("Fetch error: " + err.Error())
}
}()
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)
}
}
return result, nil
}