|
6 | 6 |
|
7 | 7 | Email notifications for ADVRider threads. Built for Cloud Run, written in Go. |
8 | 8 |
|
9 | | -ADVRider's built-in notifications only email you once after your last visit to that thread. This keeps the party going by emailing every new post until you unsubscribe. |
| 9 | +ADVRider's built-in notifications only email you once after your last visit. This keeps the conversation going by emailing every new post until you unsubscribe. |
10 | 10 |
|
11 | | -## What it does |
| 11 | +## How it works |
12 | 12 |
|
13 | | -Subscribe to ADVRider forum threads and get emails when new posts appear. That's it. |
| 13 | +Subscribe to any ADVRider thread and get emails when new posts appear. |
14 | 14 |
|
15 | | -- Adaptive polling (5min to 4hr based on thread activity) |
16 | | -- Multiple threads per email address |
17 | | -- Handles login-required forums gracefully (reports 403, doesn't crash) |
18 | | -- Exponential backoff with retry on transient failures |
19 | | -- Local dev mode with mocked email |
| 15 | +**Respectful polling:** Adaptive intervals from 5 minutes (active threads) to 4 hours (quiet threads) using exponential backoff. Each thread is scraped once per cycle regardless of subscriber count, minimizing server load. |
20 | 16 |
|
21 | | -## Running it |
| 17 | +**User limits:** Maximum 20 threads per email address. Notifications batch up to 10 posts to prevent spam. |
22 | 18 |
|
23 | | -**Local (filesystem + mock email):** |
24 | | -```bash |
25 | | -go run . |
26 | | -``` |
| 19 | +**Security:** Rate limited to 5 requests/second per IP. Token-based subscription management. Thread verification before subscription. Email content sanitized to prevent XSS and phishing. |
27 | 20 |
|
28 | | -Visit http://localhost:8080 and subscribe to a thread. |
| 21 | +**Email quality:** Dark mode support, WCAG AA compliant, clickable post anchors linking directly to specific posts. |
| 22 | + |
| 23 | +## Running locally |
29 | 24 |
|
30 | | -**Cloud Run (GCS + Brevo):** |
31 | 25 | ```bash |
32 | | -export STORAGE_BUCKET=your-bucket-name |
33 | | -export BASE_URL=https://your-service.run.app |
34 | | -export BREVO_API_KEY=your-api-key |
35 | | -ko apply -f service.yaml |
| 26 | +go run . |
36 | 27 | ``` |
37 | 28 |
|
38 | | -Requires a service account with Cloud Storage access and a Brevo API key. |
| 29 | +Visit http://localhost:8080 and subscribe to a thread. |
39 | 30 |
|
40 | 31 | ## Architecture |
41 | 32 |
|
42 | 33 | ``` |
43 | | -/ Homepage (subscribe form) |
44 | | -/subscribe POST: Create subscription (verifies thread exists first) |
| 34 | +/ Subscribe form |
| 35 | +/subscribe Create subscription (verifies thread exists) |
45 | 36 | /manage?token=... View/delete subscriptions |
46 | | -/pollz POST: Trigger immediate poll (no auth, rate limited by IP) |
47 | | -``` |
48 | | - |
49 | | -Storage is either local filesystem (`./data`) or GCS (`STORAGE_BUCKET`). |
50 | | - |
51 | | -Email via Brevo API. Auto-detects mock mode when `BREVO_API_KEY` is not set. |
52 | | - |
53 | | -Each thread is scraped once per poll cycle regardless of subscriber count. Polling interval calculated per-thread using exponential backoff: `5min × 2^(hours_since_post / 3)`, capped at 4 hours. |
54 | | - |
55 | | -## Security |
56 | | - |
57 | | -- Rate limiting: 5 subscriptions/hour per IP |
58 | | -- Token-based subscription management (64-char random, constant-time comparison) |
59 | | -- Thread limit: 20 per user (prevents resource exhaustion) |
60 | | -- Email limit: 10 posts per notification (prevents abuse) |
61 | | -- CSP, X-Frame-Options, X-Content-Type-Options headers |
62 | | -- Thread verification before subscription (validates URL is actually an ADVRider thread) |
63 | | - |
64 | | -## Environment Variables |
65 | | - |
66 | | -| Variable | Required | Description | |
67 | | -|----------|----------|-------------| |
68 | | -| `STORAGE_BUCKET` | Cloud only | GCS bucket for subscription data | |
69 | | -| `BASE_URL` | Cloud only | Public URL (for manage links in emails) | |
70 | | -| `BREVO_API_KEY` | Cloud only | Brevo API key for sending emails | |
71 | | -| `SALT` | Required | Secret salt for token generation (set in GSM or env) | |
72 | | -| `PORT` | Optional | HTTP port (default: 8080) | |
73 | | -| `LOCAL_STORAGE` | Optional | Local storage path (default: ./data) | |
74 | | -| `MAIL_FROM` | Optional | From email address (defaults to postmaster@domain) | |
75 | | -| `MAIL_NAME` | Optional | From name (default: ADVRider Notifier) | |
76 | | - |
77 | | -## Development |
78 | | - |
79 | | -```bash |
80 | | -make build # Build binary |
81 | | -make test # Run tests |
82 | | -make lint # golangci-lint |
83 | | -make deploy # Deploy to Cloud Run via ko |
| 37 | +/pollz Trigger poll (rate limited) |
84 | 38 | ``` |
85 | 39 |
|
86 | | -Tests use real ADVRider HTML parsing (no mocks for scraper). Exponential backoff algorithm is tested for correctness across all activity levels. |
87 | | - |
88 | | -## Email Templates |
89 | | - |
90 | | -Dark mode support via `prefers-color-scheme`. WCAG AA compliant. Post numbers are clickable anchors to specific posts on specific pages. |
91 | | - |
92 | | -Footer links: "View thread" (goes to last page + last post anchor) and "Manage" (token-authenticated subscription management). |
93 | | - |
94 | | -## Why Go? |
| 40 | +Storage: Local filesystem (`./data`) or GCS. Email via Brevo API (auto-mocks when `BREVO_API_KEY` not set). |
95 | 41 |
|
96 | | -Fast cold starts on Cloud Run, trivial deploys with ko, stdlib has everything we need. No npm, no containers, no Dockerfile. |
| 42 | +Polling interval: `5min × 2^(hours_since_post / 3)`, capped at 4 hours. |
97 | 43 |
|
98 | 44 | ## License |
99 | 45 |
|
|
0 commit comments