diff --git a/internal/api/search_handlers.go b/internal/api/search_handlers.go index 2fb858c..31cb4a0 100644 --- a/internal/api/search_handlers.go +++ b/internal/api/search_handlers.go @@ -173,12 +173,13 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) { } } - // Batch-load thread info for all hits + // Batch-load thread info and received_at fallback for all hits hitIDs := make([]string, len(result.Hits)) for i, h := range result.Hits { hitIDs[i] = h.ID } threadInfo, _ := s.store.GetThreadInfo(r.Context(), hitIDs) + receivedAts := s.store.GetReceivedAts(r.Context(), hitIDs) enriched := make([]enrichedHit, 0, len(result.Hits)) for _, h := range result.Hits { @@ -193,6 +194,8 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) { eh.Subject = pm.Subject if !pm.Date.IsZero() { eh.Date = pm.Date.UTC().Format(time.RFC3339) + } else if t, ok := receivedAts[h.ID]; ok && !t.IsZero() { + eh.Date = t.UTC().Format(time.RFC3339) } eh.HasAttachments = len(pm.Attachments) > 0 diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 3fc6385..7822752 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -682,6 +682,31 @@ func (s *Store) Delete(id string) error { return nil } +// GetReceivedAts returns the received_at timestamp for each mail ID in the +// provided slice. Used as a date fallback when the email Date header cannot +// be parsed. Missing IDs are silently omitted from the result map. +func (s *Store) GetReceivedAts(ctx context.Context, ids []string) map[string]time.Time { + if s.db == nil || len(ids) == 0 { + return nil + } + // pgx supports $1 = []string via ANY + rows, err := s.db.Query(ctx, + `SELECT id, received_at FROM emails WHERE id = ANY($1)`, ids) + if err != nil { + return nil + } + defer rows.Close() + result := make(map[string]time.Time, len(ids)) + for rows.Next() { + var id string + var t time.Time + if err := rows.Scan(&id, &t); err == nil { + result[id] = t + } + } + return result +} + // Purge deletes all mails whose retain_until has passed. // Returns the number of successfully deleted mails. // Mails that fail to delete (e.g. file missing) are skipped silently. diff --git a/pkg/mailparser/parser.go b/pkg/mailparser/parser.go index 512beec..a78293f 100644 --- a/pkg/mailparser/parser.go +++ b/pkg/mailparser/parser.go @@ -117,6 +117,16 @@ func Parse(raw []byte) (*ParsedMail, error) { "02 Jan 2006 15:04:05 -0700", "Mon, 2 Jan 2006 15:04:05 MST", "Mon, 02 Jan 2006 15:04:05 MST", + // Colon in timezone offset (e.g. "+02:00") used by some MTA versions + "Mon, 2 Jan 2006 15:04:05 -07:00", + "Mon, 02 Jan 2006 15:04:05 -07:00", + "2 Jan 2006 15:04:05 -07:00", + "02 Jan 2006 15:04:05 -07:00", + // Without seconds + "Mon, 2 Jan 2006 15:04 -0700", + "Mon, 02 Jan 2006 15:04 -0700", + "2 Jan 2006 15:04 -0700", + // Go stdlib aliases time.RFC1123Z, time.RFC1123, } {