@@ -282,8 +282,8 @@ func searchLimit(query backend.SearchQuery) uint32 {
return 100
}
-func (p *Provider) FetchEmailBody(_ context.Context, _ string, uid uint32) (string, string, []backend.Attachment, error) {
- jmapID, err := p.lookupJMAPID(uid)
+func (p *Provider) FetchEmailBody(_ context.Context, folder string, uid uint32) (string, string, []backend.Attachment, error) {
+ jmapID, err := p.resolveUID(folder, uid)
if err != nil {
return "", "", nil, err
}
@@ -362,8 +362,8 @@ func (p *Provider) FetchAttachment(_ context.Context, _ string, _ uint32, partID
return io.ReadAll(reader)
}
-func (p *Provider) MarkAsRead(_ context.Context, _ string, uid uint32) error {
- jmapID, err := p.lookupJMAPID(uid)
+func (p *Provider) MarkAsRead(_ context.Context, folder string, uid uint32) error {
+ jmapID, err := p.resolveUID(folder, uid)
if err != nil {
return err
}
@@ -380,8 +380,8 @@ func (p *Provider) MarkAsRead(_ context.Context, _ string, uid uint32) error {
return err
}
-func (p *Provider) MarkAsUnread(_ context.Context, _ string, uid uint32) error {
- jmapID, err := p.lookupJMAPID(uid)
+func (p *Provider) MarkAsUnread(_ context.Context, folder string, uid uint32) error {
+ jmapID, err := p.resolveUID(folder, uid)
if err != nil {
return err
}
@@ -398,8 +398,8 @@ func (p *Provider) MarkAsUnread(_ context.Context, _ string, uid uint32) error {
return err
}
-func (p *Provider) DeleteEmail(_ context.Context, _ string, uid uint32) error {
- jmapID, err := p.lookupJMAPID(uid)
+func (p *Provider) DeleteEmail(_ context.Context, folder string, uid uint32) error {
+ jmapID, err := p.resolveUID(folder, uid)
if err != nil {
return err
}
@@ -428,8 +428,8 @@ func (p *Provider) DeleteEmail(_ context.Context, _ string, uid uint32) error {
return err
}
-func (p *Provider) ArchiveEmail(_ context.Context, _ string, uid uint32) error {
- jmapID, err := p.lookupJMAPID(uid)
+func (p *Provider) ArchiveEmail(_ context.Context, folder string, uid uint32) error {
+ jmapID, err := p.resolveUID(folder, uid)
if err != nil {
return err
}
@@ -450,8 +450,8 @@ func (p *Provider) ArchiveEmail(_ context.Context, _ string, uid uint32) error {
return err
}
-func (p *Provider) MoveEmail(_ context.Context, uid uint32, _, dstFolder string) error {
- jmapID, err := p.lookupJMAPID(uid)
+func (p *Provider) MoveEmail(_ context.Context, uid uint32, srcFolder, dstFolder string) error {
+ jmapID, err := p.resolveUID(srcFolder, uid)
if err != nil {
return err
}
@@ -678,6 +678,55 @@ func (p *Provider) Close() error {
// Verify interface compliance at compile time.
var _ backend.Provider = (*Provider)(nil)
+// resolveUID returns the JMAP ID for the given uint32 UID. It checks the
+// in-memory cache first (fast path when FetchEmails ran on the same instance),
+// then falls back to querying the mailbox so the backend works correctly even
+// when a fresh Provider instance is created per call.
+func (p *Provider) resolveUID(folder string, uid uint32) (jmapclient.ID, error) {
+ if id, err := p.lookupJMAPID(uid); err == nil {
+ return id, nil
+ }
+ return p.resolveUIDByQuery(folder, uid)
+}
+
+// resolveUIDByQuery fetches all email IDs in the folder from JMAP via
+// Email/query, hashes each one, and returns the ID whose hash matches uid.
+// It also warms the local cache as a side effect.
+func (p *Provider) resolveUIDByQuery(folder string, uid uint32) (jmapclient.ID, error) {
+ mboxID, err := p.resolveMailboxID(folder)
+ if err != nil {
+ return "", fmt.Errorf("jmap: resolving mailbox for UID lookup: %w", err)
+ }
+
+ req := &jmapclient.Request{}
+ req.Invoke(&email.Query{
+ Account: p.accountID,
+ Filter: &email.FilterCondition{InMailbox: mboxID},
+ Limit: 10000,
+ })
+
+ resp, err := p.client.Do(req)
+ if err != nil {
+ return "", fmt.Errorf("jmap: querying IDs for UID lookup: %w", err)
+ }
+
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ for _, inv := range resp.Responses {
+ if r, ok := inv.Args.(*email.QueryResponse); ok {
+ for _, id := range r.IDs {
+ h := jmapIDToUID(id)
+ p.idToJMAPID[h] = id
+ if h == uid {
+ return id, nil
+ }
+ }
+ }
+ }
+ return "", fmt.Errorf("jmap: no email found for UID %d in folder %q", uid, folder)
+}
+
// lookupJMAPID resolves a uint32 UID hash back to the JMAP string ID.
func (p *Provider) lookupJMAPID(uid uint32) (jmapclient.ID, error) {
p.mu.Lock()