mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
483 lines
13 KiB
Markdown
483 lines
13 KiB
Markdown
# 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.
|