This commit is contained in:
Tomáš Dvořák
2025-10-16 13:32:05 +02:00
commit 12cba639b9
663 changed files with 168914 additions and 0 deletions
+482
View File
@@ -0,0 +1,482 @@
# 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": "<html>...</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.