# Newsletter System Documentation ## Overview The comprehensive newsletter system automatically sends targeted emails to subscribers based on their preferences. It supports multiple newsletter types with category-based filtering and email analytics tracking. ## Newsletter Types ### 1. **Týdenní Přehled (Weekly Overview)** - **When**: Configurable day and hour (default: Sunday 9 AM) - **Content**: Blogs, activities, matches, scores from the past week - **Preferences**: User can select which content types to include - **Settings**: `EnableWeekly`, `NewsletterWeeklyDay`, `NewsletterWeeklyHour` ### 2. **Nadcházející Zápasy (Upcoming Matches)** - **When**: - 48 hours before match (default, configurable via `NewsletterReminderLeadHours`) - Day of match (0-6 hours before kickoff) - **Content**: Match details (teams, date, time, competition, venue) - **Preferences**: Filter by categories/competitions - **Settings**: `EnableMatchReminders`, `NewsletterReminderLeadHours` ### 3. **Nový Blog (Blog Release Notification)** - **When**: Immediately when article is published - **Content**: Article title, excerpt, link to full article (tracked) - **Preferences**: Filter by blog categories - **Trigger**: Automatic when article `published` field changes from `false` to `true` ### 4. **Výsledky Zápasů (Match Results)** - **When**: Within 6 hours after match finishes (respects quiet hours) - **Content**: Final score, match details - **Preferences**: Filter by categories/competitions - **Settings**: `EnableResults`, `NewsletterQuietStart`, `NewsletterQuietEnd` ## User Subscription Flow ### Initial Subscription 1. User submits email via newsletter form 2. System creates `NewsletterSubscription` with default preferences: ```json { "weekly": true, "matches": true, "blogs": true, "events": true } ``` 3. Two emails sent: - **Setup email**: Token link to preference configuration page - **Welcome email**: Introduction to newsletter with manage/unsubscribe links ### Preference Management - Token-based (no login required) - User can enable/disable: - Weekly digest - Match reminders - Blog notifications - Match results - Event notifications - User can specify category filters (comma-separated) ### Unsubscribe - Link in every email footer - Sets `is_active = false` in database - No emails sent to inactive subscribers ## Database Schema ### Tables Added #### `newsletter_sent_log` Tracks all sent newsletters: ```sql CREATE TABLE newsletter_sent_log ( id SERIAL PRIMARY KEY, newsletter_type VARCHAR(50) NOT NULL, subject VARCHAR(500), content_ids TEXT, recipients_count INT DEFAULT 0, sent_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); ``` #### `match_notifications` Prevents duplicate match alerts: ```sql CREATE TABLE match_notifications ( id SERIAL PRIMARY KEY, match_id VARCHAR(255) NOT NULL, notification_type VARCHAR(50) NOT NULL, sent_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), recipients_count INT DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), UNIQUE(match_id, notification_type) ); ``` #### `blog_notifications` Prevents duplicate blog alerts: ```sql CREATE TABLE blog_notifications ( id SERIAL PRIMARY KEY, article_id INT NOT NULL, sent_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), recipients_count INT DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), UNIQUE(article_id) ); ``` ### Existing Tables Used #### `newsletter_subscriptions` ```sql CREATE TABLE newsletter_subscriptions ( id SERIAL PRIMARY KEY, email VARCHAR(255) NOT NULL UNIQUE, is_active BOOLEAN DEFAULT TRUE, preferences JSONB DEFAULT '{}'::jsonb, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); ``` #### `email_log` & `email_event` Track email delivery and user interactions (opens, clicks). ## Admin Configuration ### Settings (in `settings` table) | Field | Type | Description | Default | |-------|------|-------------|---------| | `newsletter_enabled` | boolean | Master toggle for all automated newsletters | `false` | | `enable_weekly` | boolean | Enable weekly digest | `false` | | `enable_match_reminders` | boolean | Enable upcoming match notifications | `false` | | `enable_results` | boolean | Enable match result notifications | `false` | | `newsletter_weekly_day` | string | Day for weekly digest (sun/mon/tue/wed/thu/fri/sat) | `"sun"` | | `newsletter_weekly_hour` | int | Hour for weekly digest (0-23) | `9` | | `newsletter_reminder_lead_hours` | int | Hours before match to send first reminder | `48` | | `newsletter_quiet_start` | int | Quiet hours start (0-23) | `22` | | `newsletter_quiet_end` | int | Quiet hours end (0-23) | `8` | ### Admin API Endpoints - `PATCH /api/v1/admin/newsletter/enable` - Toggle automation on/off - `GET /api/v1/admin/newsletter/status` - Get subscriber stats and schedule info - `POST /api/v1/admin/newsletter/test` - Send test email - `POST /api/v1/admin/newsletter/send-digest` - Manually send digest - `POST /api/v1/admin/newsletter/preview` - Preview newsletter content - `GET /api/v1/admin/newsletter/subscribers` - List subscribers - `PATCH /api/v1/admin/newsletter/subscribers/:id/status` - Enable/disable subscriber - `DELETE /api/v1/admin/newsletter/subscribers/:id` - Delete subscriber ## Email Analytics & Tracking Every newsletter email includes: ### Open Tracking - 1x1 transparent pixel: `/api/v1/email/open.gif?m={email_log_id}&t={token}` - Logged in `email_event` table with `event_type = "open"` ### Click Tracking - All links wrapped: `/api/v1/email/click?m={email_log_id}&t={token}&u={destination_url}` - Logged in `email_event` table with `event_type = "click"` - Redirects user to actual destination ### Unsubscribe Tracking - Unsubscribe link logs event before deactivating subscription ### Blog Links - Direct links to articles with tracking parameters - Includes subscriber token for personalization ## Implementation Details ### Service Architecture #### `newsletter_automation.go` Main automation service that: - Runs every 15 minutes - Checks settings to determine enabled newsletter types - Loads match/blog data from cache (`cache/prefetch/`) - Queries subscribers with matching preferences - Sends personalized emails - Records notifications to prevent duplicates #### Key Functions - `Start()` - Initializes automation loop - `RunCycle()` - Checks all newsletter types - `checkWeeklyDigest()` - Weekly newsletter logic - `checkUpcomingMatches()` - Match reminder logic - `checkFinishedMatches()` - Match result logic - `SendBlogNotification(article)` - Blog notification (called from controller) ### Blog Notification Trigger When an article is created or updated: ```go // In base_controller.go CreateArticle/UpdateArticle if art.Published { go bc.triggerBlogNotification(&art) } ``` ### Content Generation #### `newsletter_content.go` - `BuildNewsletterDigest()` - Builds HTML digest from cached JSON - Reads from: `articles.json`, `events_upcoming.json`, `facr_club_info.json` - Respects user preferences and category filters - Returns subject and HTML content ### Preference Format Stored in `preferences` JSONB field: ```json { "weekly": true, "matches": true, "blogs": true, "events": true, "scores": true, "categories": "MFS A,MFS B,Divize" } ``` ## Email Templates ### Templates Directory `templates/emails/` - `base.html` - Base template with branding - `newsletter.html` - General newsletter template - `newsletter_welcome.html` - Welcome email - `newsletter_welcome_back.html` - Resubscribe email - `newsletter_setup.html` - Initial setup email ### Template Variables All templates receive: - `ClubName`, `ClubLogoURL` - `PrimaryColor`, `SecondaryColor`, `AccentColor` - `FacebookURL`, `InstagramURL`, `YouTubeURL`, `TwitterURL` - `UnsubscribeURL`, `ManageURL` - `OpenPixelURL` (for tracking) - `ContactEmail`, `ContactURL`, `WebsiteURL` ## Testing ### Manual Test ```bash # Send test newsletter curl -X POST http://localhost:8080/api/v1/admin/newsletter/test \ -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "email": "test@example.com", "type": "weekly" }' ``` ### Available Test Types - `newsletter` - Basic test - `welcome` - Welcome email - `welcome_back` - Resubscribe email - `blogs` - Blog digest - `events` - Events digest - `matches` - Upcoming matches - `scores` - Recent results - `weekly` - Full weekly digest ## Migration ### Running Migrations ```bash # Set environment variable export RUN_MIGRATIONS=true # Start application (runs migrations automatically) ./main ``` ### Migration File `database/migrations/20250930000001_enhance_newsletter_system.up.sql` Creates tracking tables for newsletter automation. ## Environment Variables ```bash # Master toggle (can be overridden by DB settings) NEWSLETTER_ENABLED=true # Legacy interval for old scheduler (hours) NEWSLETTER_INTERVAL_HOURS=24 ``` ## Security Considerations 1. **Token-Based Access**: Subscriber tokens expire after 30 days 2. **Rate Limiting**: 200ms delay between emails to avoid SMTP throttling 3. **Email Validation**: Validates email format before subscription 4. **Duplicate Prevention**: Unique constraints on notification tracking tables 5. **SMTP Authentication**: Uses configured SMTP with TLS/SSL 6. **Tracking Links**: Uses secure tokens to prevent unauthorized access ## Troubleshooting ### No Emails Being Sent 1. Check `newsletter_enabled` in settings table: ```sql SELECT newsletter_enabled FROM settings LIMIT 1; ``` 2. Check specific newsletter type enabled: ```sql SELECT enable_weekly, enable_match_reminders, enable_results FROM settings LIMIT 1; ``` 3. Check active subscribers: ```sql SELECT COUNT(*) FROM newsletter_subscriptions WHERE is_active = true; ``` 4. Check SMTP configuration: ```sql SELECT smtp_host, smtp_port, smtp_from FROM settings LIMIT 1; ``` ### Duplicate Emails - Check notification tracking tables for existing entries - Unique constraints should prevent duplicates automatically ### Wrong Schedule - Verify `newsletter_weekly_day` and `newsletter_weekly_hour` in settings - Server timezone may differ from expected timezone ## Future Enhancements Potential improvements: - [ ] A/B testing for subject lines - [ ] Email template editor in admin UI - [ ] Subscriber segmentation (active/inactive/engaged) - [ ] Bounce handling and automatic cleanup - [ ] Personalized content recommendations - [ ] Mobile-responsive email templates (already inline CSS, but could improve) - [ ] Newsletter archive on website - [ ] RSS-to-email automation ## API Reference ### Public Endpoints (No Auth Required) #### Subscribe ```http POST /api/v1/newsletter/subscribe Content-Type: application/json { "email": "user@example.com", "preferences": { "weekly": true, "matches": true, "blogs": true } } ``` #### Get Preferences (with token) ```http GET /api/v1/newsletter/preferences?token={subscriber_token} ``` #### Save Preferences (with token) ```http POST /api/v1/newsletter/preferences Content-Type: application/json { "token": "{subscriber_token}", "preferences": { "weekly": true, "matches": false, "blogs": true, "categories": "MFS A,Divize" } } ``` #### Unsubscribe (with token) ```http POST /api/v1/newsletter/unsubscribe-token Content-Type: application/json { "token": "{subscriber_token}" } ``` ### Admin Endpoints (Requires Auth) All admin endpoints require `Authorization: Bearer {admin_jwt_token}` header. #### Enable/Disable Automation ```http PATCH /api/v1/admin/newsletter/enable Content-Type: application/json { "enabled": true } ``` #### Get Status ```http GET /api/v1/admin/newsletter/status ``` Response: ```json { "total_subscribers": 150, "active_subscribers": 142, "sample_recipients": ["email1@example.com", "email2@example.com"], "interval_minutes": 1440, "next_approximate": "2025-10-01T09:00:00Z", "newsletter_enabled": true } ``` #### Send Test Email ```http POST /api/v1/admin/newsletter/test Content-Type: application/json { "email": "admin@example.com", "type": "weekly" } ``` #### Manual Digest Send ```http POST /api/v1/admin/newsletter/send-digest Content-Type: application/json { "type": "weekly", "competitions": "MFS A, MFS B" } ``` #### Preview Newsletter ```http POST /api/v1/admin/newsletter/preview Content-Type: application/json { "preferences": { "blogs": true, "matches": true, "competitions": "MFS A" } } ``` Response: ```json { "subject": "Fotbal Club – novinky a zápasy", "html": "..." } ``` ## Conclusion This newsletter system provides a complete, automated solution for keeping subscribers informed about club activities. It respects user preferences, tracks engagement, and integrates seamlessly with the existing article and match management systems.