feat: fix the generic IMAP client so it can actually connect and sync - assign the client before auth (was nil and would panic), check CONDSTORE after login where servers advertise it, request LIST-STATUS fields (folders were silently empty), and fetch 1:* instead of an empty SeqSet that panics inside go-imap

This commit is contained in:
Matthew Meszaros
2026-07-11 17:30:20 +02:00
parent 932b0bc856
commit ee35138e37
+25 -9
View File
@@ -66,12 +66,7 @@ func (c *Client) Connect() *errx.MailError {
return errx.ErrMailServerUnreachable
}
client := imapclient.New(conn, nil)
caps := client.Caps()
if !caps.Has(imap.CapCondStore) {
return errx.ErrMailCondStoreNotSupported
}
c.client = imapclient.New(conn, nil)
var xerr *errx.MailError
@@ -81,8 +76,18 @@ func (c *Client) Connect() *errx.MailError {
case models.AuthOAuth2:
xerr = c.oauth2Auth()
}
if xerr != nil {
return xerr
}
return xerr
// CONDSTORE backs the ChangedSince incremental sync. Servers (Gmail,
// Dovecot, ...) typically advertise it only after authentication, so the
// check must run post-auth.
if !c.client.Caps().Has(imap.CapCondStore) {
return errx.ErrMailCondStoreNotSupported
}
return nil
}
func (c *Client) Close() error {
@@ -126,7 +131,14 @@ func (c *Client) oauth2Auth() *errx.MailError {
func (c *Client) Folders() ([]models.Mailbox, *errx.MailError) {
var resp []models.Mailbox
cmd := c.client.List("", "%", nil)
// LIST-STATUS: without requesting these, f.Status is nil for every
// folder and the sync loop sees an empty account.
cmd := c.client.List("", "%", &imap.ListOptions{
ReturnStatus: &imap.StatusOptions{
UIDValidity: true,
HighestModSeq: true,
},
})
for f := cmd.Next(); f != nil; f = cmd.Next() {
if len(resp) >= config.MaxEmailFolders {
@@ -167,7 +179,11 @@ func (c *Client) Mailbox(mailbox string, uidvali, opts *imap.SelectOptions) erro
}
func (c *Client) FetchChanges(ctx context.Context, lastModSeq uint64) *errx.MailError {
cmd := c.client.Fetch(&imap.SeqSet{}, &imap.FetchOptions{
// 1:* — an empty SeqSet has no encodable ranges and panics inside
// go-imap; ChangedSince narrows the result server-side.
var allMessages imap.SeqSet
allMessages.AddRange(1, 0)
cmd := c.client.Fetch(allMessages, &imap.FetchOptions{
UID: true,
Envelope: true,
BodyStructure: &imap.FetchItemBodyStructure{