diff --git a/internal/storage/storage.go b/internal/storage/storage.go index ddeb729..534414d 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -583,11 +583,15 @@ func (s *Store) insertMeta(ctx context.Context, id string, pm *mailparser.Parsed msgID = &pm.MessageID } + receivedAt := pm.Date + if receivedAt.IsZero() { + receivedAt = time.Now() + } _, err := s.db.Exec(ctx, ` INSERT INTO emails (id, received_at, mail_from, mail_to, subject, size_bytes, has_attach, tenant_id, message_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (id) DO NOTHING - `, id, pm.Date, pm.From, mailTo, pm.Subject, int64(size), hasAttach, tenantID, msgID) + `, id, receivedAt, pm.From, mailTo, pm.Subject, int64(size), hasAttach, tenantID, msgID) return err } @@ -615,6 +619,10 @@ func (s *Store) SaveMeta(ctx context.Context, id string, pm *mailparser.ParsedMa msgID = &pm.MessageID } + metaDate := pm.Date + if metaDate.IsZero() { + metaDate = time.Now() + } _, err := s.db.Exec(ctx, ` INSERT INTO emails (id, received_at, mail_from, mail_to, subject, size_bytes, has_attach, message_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) @@ -625,7 +633,7 @@ func (s *Store) SaveMeta(ctx context.Context, id string, pm *mailparser.ParsedMa size_bytes = EXCLUDED.size_bytes, has_attach = EXCLUDED.has_attach, message_id = COALESCE(emails.message_id, EXCLUDED.message_id) - `, id, pm.Date, pm.From, mailTo, pm.Subject, int64(size), hasAttach, msgID) + `, id, metaDate, pm.From, mailTo, pm.Subject, int64(size), hasAttach, msgID) return err } diff --git a/pkg/mailparser/parser.go b/pkg/mailparser/parser.go index 9fb5e65..2f5e5f9 100644 --- a/pkg/mailparser/parser.go +++ b/pkg/mailparser/parser.go @@ -81,11 +81,38 @@ func Parse(raw []byte) (*ParsedMail, error) { msgID := msg.Header.Get("Message-Id") pm.MessageID = strings.Trim(msgID, "<>") - // Date + // Date — try go-message parser first, then fallback formats, then zero if d, err := msg.Header.Date(); err == nil { pm.Date = d } else { - pm.Date = time.Now() + // Some MUAs emit non-standard variants (e.g. "+0100 (CET)" suffix). + // Try common RFC 2822 / non-standard formats before giving up. + raw := strings.TrimSpace(msg.Header.Get("Date")) + // Strip parenthesised timezone comment: "... +0100 (CET)" → "... +0100" + if idx := strings.LastIndex(raw, "("); idx > 0 { + raw = strings.TrimSpace(raw[:idx]) + } + parsed := false + for _, layout := range []string{ + "Mon, 2 Jan 2006 15:04:05 -0700", + "Mon, 02 Jan 2006 15:04:05 -0700", + "2 Jan 2006 15:04:05 -0700", + "02 Jan 2006 15:04:05 -0700", + "Mon, 2 Jan 2006 15:04:05 MST", + "Mon, 02 Jan 2006 15:04:05 MST", + time.RFC1123Z, + time.RFC1123, + } { + if t, err := time.Parse(layout, raw); err == nil { + pm.Date = t + parsed = true + break + } + } + if !parsed { + // Leave pm.Date as zero — storage will use DB DEFAULT NOW() + pm.Date = time.Time{} + } } // Parse body / MIME parts