Files
MyClub/DOCS/NEWSLETTER_SYSTEM.md
T
Tomáš Dvořák 12cba639b9 upload
2025-10-16 13:32:05 +02:00

483 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.