Complete guide to the Bulk Mailer system architecture and API reference.
The Bulk Mailer is a scalable email distribution system built on Cloudflare Workers with Next.js 14 and TypeScript.
cd bulk-mailer
npm install
npm run devCreate a .env.local file with the following variables:
JWT_SECRET=your-secret-here
ENCRYPTION_KEY=your-encryption-key-here
CSRF_SECRET=your-csrf-secret-here
SMTP_SERVER=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USERNAME=apikey
SMTP_PASSWORD=your-sendgrid-api-key
FROM_EMAIL=noreply@yourdomain.comnpm run devThe application will start on http://localhost:3000
The system follows a three-phase architecture optimized for edge computing and scalability.
| Component | Technology | Purpose |
|---|---|---|
| Runtime | Cloudflare Workers | Edge computing |
| Framework | Next.js 14 | React framework |
| Language | TypeScript | Type safety |
| Database | Durable Objects | Phase 3 storage |
| Cache | KV Store | Phase 3 caching |
| Nodemailer | SMTP sending |
interface JWTPayload {
userId: string
email: string
role: string
iat: number
exp: number
}All state-changing operations (POST, PUT, DELETE, PATCH) require CSRF token validation:
/api/auth/registerRegister new user
Request Body:
{
name: string
email: string
password: string
}Response:
{
success: boolean
data: {
user: User
token: string
csrfToken: string
}
}/api/auth/loginAuthenticate user
Request Body:
{
email: string
password: string
}Response:
{
success: boolean
data: {
user: User
token: string
csrfToken: string
}
}/api/smtp/providersList SMTP providers
Authentication: Required
Response:
{
success: boolean
data: SMTPProvider[]
}/api/smtp/providersCreate SMTP provider
Authentication: Required + CSRF
Request Body:
{
name: string
host: string
port: number
username: string
password: string
secure: 'tls' | 'ssl'
dailyLimit: number
}Response:
{
success: boolean
data: SMTPProvider
}/api/contactsList contacts with pagination
Authentication: Required
Query Parameters: page, limit
/api/contactsCreate contact
Authentication: Required + CSRF
Request Body:
{
email: string
firstName?: string
lastName?: string
tags?: string[]
}/api/campaignsList campaigns with pagination
Authentication: Required
Query Parameters: page, limit
/api/campaignsCreate campaign
Authentication: Required + CSRF
Request Body:
{
name: string
subject: string
template: string
scheduledAt?: string
}Current implementation uses in-memory storage with singleton pattern for development and testing.
| Entity | Fields | Indexed By |
|---|---|---|
| User | id, email, passwordHash, createdAt | |
| SMTPProvider | id, userId, host, port, username, password (encrypted) | userId |
| Contact | id, userId, email, firstName, lastName, tags, status | userId, email |
| Campaign | id, userId, name, subject, template, status | userId |
| EmailQueue | id, campaignId, contactId, status, attempts | campaignId |
Persistent storage will use Cloudflare Durable Objects with the following structure:
Sensitive data (SMTP passwords) is encrypted using AES-256-GCM before storage:
import { encryptData, decryptData } from '@/lib/encryption'
const encrypted = await encryptData(smtpPassword)
const decrypted = await decryptData(encrypted)| Header | Value | Purpose |
|---|---|---|
| Strict-Transport-Security | max-age=31536000; includeSubDomains | Force HTTPS |
| Content-Security-Policy | Restrictive policy | Prevent XSS |
| X-Frame-Options | DENY | Prevent clickjacking |
| X-Content-Type-Options | nosniff | Prevent MIME sniffing |
All endpoints validate input using Zod schemas:
import { z } from 'zod'
const contactSchema = z.object({
email: z.string().email('Invalid email'),
firstName: z.string().optional(),
tags: z.array(z.string()).default([])
})
const data = contactSchema.parse(body)npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage reportTest suites: Auth (8 tests), CSRF (7 tests), Utils (7 tests), Encryption (2 tests)
Test API endpoints with request validation and response verification:
import request from 'supertest'
import { POST } from '@/app/api/contacts/route'
describe('Contacts API', () => {
it('should create contact with CSRF token', async () => {
const res = await request(app)
.post('/api/contacts')
.set('Authorization', `Bearer ${token}`)
.set('X-CSRF-Token', csrfToken)
.send({ email: 'test@example.com' })
expect(res.status).toBe(200)
})
})Configure these in Cloudflare Pages dashboard, never commit to git:
Ensure X-CSRF-Token header is included in state-changing requests:
fetch('/api/contacts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'X-CSRF-Token': csrfToken
},
body: JSON.stringify(data)
})Check that credentials are correct and account exists. In Phase 2 (in-memory), data is lost on server restart.
Verify SMTP credentials, check server logs, ensure contact and campaign exist, and rate limits not exceeded.
npm run type-checkNeed help? Reach out through these channels: