4.0 KiB
4.0 KiB
| id | title | status | assignee | created_date | updated_date | labels | dependencies | priority | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| task-015 | Set up Cloudflare D1 email-collector database for cross-site subscriptions | To Do | 2025-12-04 12:00 | 2025-12-04 12:03 |
|
medium |
Description
Create a standalone Cloudflare D1 database for collecting email subscriptions across all websites (mycofi.earth, canvas.jeffemmett.com, decolonizeti.me, etc.) with easy export capabilities.
Purpose:
- Unified email collection from all sites
- Page-separated lists (e.g., /newsletter, /waitlist, /landing)
- Simple CSV/JSON export for email campaigns
- GDPR-compliant with unsubscribe tracking
Sites to integrate:
- mycofi.earth
- canvas.jeffemmett.com
- decolonizeti.me
- games.jeffemmett.com
- Future sites
Key Features:
- Double opt-in verification
- Source tracking (which site, which page)
- Export in multiple formats (CSV, JSON, Mailchimp)
- Basic admin dashboard or CLI for exports
- Rate limiting to prevent abuse
Acceptance Criteria
- #1 D1 database 'email-collector' created on Cloudflare
- #2 Schema deployed with subscribers, verification_tokens tables
- #3 POST /api/subscribe endpoint accepts email + source_site + source_page
- #4 Email verification flow with token-based double opt-in
- #5 GET /api/emails/export returns CSV with filters (site, date, verified)
- #6 Unsubscribe endpoint and tracking
- #7 Rate limiting prevents spam submissions
- #8 At least one site integrated and collecting emails
Implementation Plan
Implementation Steps
1. Create D1 Database
wrangler d1 create email-collector
2. Create Schema File
Create worker/email-collector-schema.sql:
-- Email Collector Schema
-- Cross-site email subscription management
CREATE TABLE IF NOT EXISTS subscribers (
id TEXT PRIMARY KEY,
email TEXT NOT NULL,
email_hash TEXT NOT NULL, -- For duplicate checking
source_site TEXT NOT NULL,
source_page TEXT,
referrer TEXT,
ip_country TEXT,
subscribed_at TEXT DEFAULT (datetime('now')),
verified INTEGER DEFAULT 0,
verified_at TEXT,
unsubscribed INTEGER DEFAULT 0,
unsubscribed_at TEXT,
metadata TEXT -- JSON for custom fields
);
CREATE TABLE IF NOT EXISTS verification_tokens (
id TEXT PRIMARY KEY,
email TEXT NOT NULL,
token TEXT UNIQUE NOT NULL,
expires_at TEXT NOT NULL,
used INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now'))
);
-- Rate limiting table
CREATE TABLE IF NOT EXISTS rate_limits (
ip_hash TEXT PRIMARY KEY,
request_count INTEGER DEFAULT 1,
window_start TEXT DEFAULT (datetime('now'))
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_subs_email_hash ON subscribers(email_hash);
CREATE INDEX IF NOT EXISTS idx_subs_site ON subscribers(source_site);
CREATE INDEX IF NOT EXISTS idx_subs_page ON subscribers(source_site, source_page);
CREATE INDEX IF NOT EXISTS idx_subs_verified ON subscribers(verified);
CREATE UNIQUE INDEX IF NOT EXISTS idx_subs_unique ON subscribers(email_hash, source_site);
CREATE INDEX IF NOT EXISTS idx_tokens_token ON verification_tokens(token);
3. Create Worker Endpoints
Create worker/emailCollector.ts:
// POST /api/subscribe
// GET /api/verify/:token
// POST /api/unsubscribe
// GET /api/emails/export (auth required)
// GET /api/emails/stats
4. Export Formats
- CSV:
email,source_site,source_page,subscribed_at,verified - JSON: Full object array
- Mailchimp: CSV with required headers
5. Admin Authentication
- Use simple API key for export endpoint
- Store in Worker secret:
EMAIL_ADMIN_KEY
6. Integration
Add to each site's signup form:
fetch('https://canvas.jeffemmett.com/api/subscribe', {
method: 'POST',
body: JSON.stringify({
email: 'user@example.com',
source_site: 'mycofi.earth',
source_page: '/newsletter'
})
})