diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go index 55ef165..96214d7 100644 --- a/scheduler/scheduler.go +++ b/scheduler/scheduler.go @@ -20,16 +20,16 @@ const ( emailsPerSecondPerUser = emailsPerMinutePerUser / 60.0 // Cleanup intervals - cleanupInterval = 24 * time.Hour - seenItemsRetention = 6 * 30 * 24 * time.Hour // 6 months - itemMaxAge = 3 * 30 * 24 * time.Hour // 3 months - emailSendsRetention = 6 * 30 // 6 months in days + cleanupInterval = 24 * time.Hour + seenItemsRetention = 6 * 30 * 24 * time.Hour // 6 months + itemMaxAge = 3 * 30 * 24 * time.Hour // 3 months + emailSendsRetention = 6 * 30 // 6 months in days // Item limits minItemsForDigest = 5 // Engagement tracking - inactivityThreshold = 90 // days without opens + inactivityThreshold = 90 // days without opens minSendsBeforeDeactivate = 3 // minimum sends before considering deactivation ) @@ -425,13 +425,13 @@ func (s *Scheduler) sendDigestAndMarkSeen(ctx context.Context, cfg *store.Config s.logger.Warn("failed to record email send", "err", err) } s.logger.Debug("sendDigestAndMarkSeen: recorded email send") - + // Build keep-alive URL keepAliveURL := "" if trackingToken != "" { keepAliveURL = s.originURL + "/keep-alive/" + trackingToken } - + // Send email - if this fails, transaction will rollback s.logger.Debug("sendDigestAndMarkSeen: calling mailer.Send", "to", cfg.Email) if err := s.mailer.Send(cfg.Email, subject, htmlBody, textBody, unsubToken, dashboardURL, keepAliveURL); err != nil { diff --git a/store/tracking.go b/store/tracking.go index 839521f..1b4dae2 100644 --- a/store/tracking.go +++ b/store/tracking.go @@ -9,16 +9,16 @@ import ( ) type EmailSend struct { - ID int64 - ConfigID int64 - Recipient string - Subject string - TrackingToken string - SentAt time.Time - Bounced bool - BounceReason sql.NullString - Opened bool - OpenedAt sql.NullTime + ID int64 + ConfigID int64 + Recipient string + Subject string + TrackingToken string + SentAt time.Time + Bounced bool + BounceReason sql.NullString + Opened bool + OpenedAt sql.NullTime } // RecordEmailSend records an email send with optional tracking token @@ -111,7 +111,7 @@ func (db *DB) GetInactiveConfigs(daysWithoutActivity int, minSends int) ([]int64 if err != nil { return nil, fmt.Errorf("query inactive configs: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() var configIDs []int64 for rows.Next() { @@ -220,4 +220,3 @@ func (db *DB) UpdateLastActive(trackingToken string) error { return nil } - diff --git a/store/tracking_test.go b/store/tracking_test.go index 2b2c5e3..df7d6da 100644 --- a/store/tracking_test.go +++ b/store/tracking_test.go @@ -8,7 +8,11 @@ import ( func TestEmailTracking(t *testing.T) { db := setupTestDB(t) - defer db.Close() + defer func() { + if err := db.Close(); err != nil { + t.Errorf("failed to close db: %v", err) + } + }() ctx := context.Background() diff --git a/web/handlers.go b/web/handlers.go index 1bbc39d..5d05a88 100644 --- a/web/handlers.go +++ b/web/handlers.go @@ -704,7 +704,7 @@ func (s *Server) handleKeepAlive(w http.ResponseWriter, r *http.Request, token s // Return success message w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, ` + if _, err := fmt.Fprintf(w, ` @@ -720,7 +720,9 @@ func (s *Server) handleKeepAlive(w http.ResponseWriter, r *http.Request, token s
✓ Success!
Your digest will stay active until %s.
-`, expiresAt) +`, expiresAt); err != nil { + s.logger.Error("failed to write response", "error", err) + } } func (s *Server) handle404(w http.ResponseWriter, r *http.Request) {