Open-source email template designer with a visual drag-and-drop builder. Self-host your email infrastructure with support for multiple email providers.
-
Visual Email Editor - Drag-and-drop builder with live preview
- 10 block types: Heading, Text, Button, Image, Divider, Spacer, Container, Columns, Avatar, HTML
- Nested containers and multi-column layouts
- Undo/redo, copy/paste, keyboard shortcuts
- Real-time HTML preview and export
-
PDF Attachments - Generate PDF versions of your templates
- Attach PDF to emails automatically
- Uses the same drag-and-drop designed templates
- Powered by Gotenberg (included in Docker setup)
-
Background Processing - Reliable email delivery
- Async email sending via job queue
- Automatic retries with exponential backoff
- Job status tracking
-
Multiple Email Providers - Connect your preferred service
- SendGrid
- Resend
- Mailgun
- Amazon SES
- Postmark
- Generic SMTP
-
Image Uploads - S3-compatible storage
- Drag-and-drop image upload in editor
- Works with MinIO (local) or AWS S3
-
Team Management - Collaborate with your team
- OAuth login (Google, GitHub)
- Role-based access control
- Multiple teams per user
-
Developer Experience
- REST API with OpenAPI documentation
- API key authentication for programmatic access
- Webhook support for email events
- Email logs and delivery tracking
+------------------+
| Web Frontend |
| (React/Vite) |
+--------+---------+
|
v
+------------------+ +------------------+ +------------------+
| PostgreSQL |<---------->| API Server |<---------->| Redis |
| (Database) | | (Fastify) | | (Queue/Cache) |
+------------------+ +--------+---------+ +------------------+
|
+------------------+------------------+
| |
v v
+------------------+ +------------------+
| Background | | Gotenberg |
| Worker |----------------->| (PDF Gen) |
+------------------+ +------------------+
|
v
+------------------+
| Email Providers |
| (SendGrid, etc) |
+------------------+
| Layer | Technology |
|---|---|
| Frontend | React, Vite, TypeScript, Tailwind CSS, Radix UI, Zustand |
| Backend | Node.js, Fastify, TypeScript, Drizzle ORM |
| Database | PostgreSQL |
| Queue | Redis + BullMQ |
| Storage | MinIO / AWS S3 |
| Gotenberg |
- Node.js >= 20
- pnpm >= 9
- Docker & Docker Compose
# 1. Clone the repository
git clone https://github.com/your-org/canary.git
cd canary
# 2. Start infrastructure services
docker compose up -d
# 3. Copy environment file
cp .env.example .env
# 4. Generate required secrets and edit .env
# ENCRYPTION_KEY (32 chars): openssl rand -hex 16
# SESSION_SECRET: openssl rand -hex 32
# 5. Install dependencies
pnpm install
# 6. Run database migrations
pnpm db:migrate
# 7. Start development servers
pnpm dev| Service | URL |
|---|---|
| Web App | http://localhost:3000 |
| API | http://localhost:3001 |
| API Docs (Swagger) | http://localhost:3001/docs |
| MinIO Console | http://localhost:9001 |
| Gotenberg | http://localhost:3100 |
# 1. Copy production environment template
cp .env.production.example .env
# 2. Edit .env with your values (required):
# - ENCRYPTION_KEY
# - SESSION_SECRET
# - POSTGRES_PASSWORD
# - Update URLs for your domain
# 3. Deploy
docker compose -f docker-compose.prod.yml up -dAll configuration is done through environment variables. Pass them to the container:
docker run -d \
-e DATABASE_URL=postgres://user:pass@host:5432/canary \
-e REDIS_URL=redis://host:6379 \
-e ENCRYPTION_KEY=your-32-char-key \
-e SESSION_SECRET=your-secret \
-e GOTENBERG_URL=http://gotenberg:3000 \
-p 3001:3001 \
canary/api:latestOr with Docker Compose, use an .env file:
services:
api:
image: canary/api:latest
env_file:
- .env
ports:
- '3001:3001'| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
REDIS_URL |
Redis connection string |
ENCRYPTION_KEY |
32-char key for encrypting secrets (generate: openssl rand -hex 16) |
SESSION_SECRET |
Secret for signing cookies (generate: openssl rand -hex 32) |
| Variable | Default | Description |
|---|---|---|
APP_URL |
http://localhost:3000 | Frontend URL |
API_URL |
http://localhost:3001 | API URL |
GOTENBERG_URL |
- | Gotenberg service URL for PDF generation |
WORKER_ENABLED |
true | Enable background worker |
WORKER_CONCURRENCY |
5 | Number of concurrent jobs |
GOOGLE_CLIENT_ID |
- | Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
- | Google OAuth secret |
GITHUB_CLIENT_ID |
- | GitHub OAuth client ID |
GITHUB_CLIENT_SECRET |
- | GitHub OAuth secret |
S3_ENDPOINT |
- | S3-compatible endpoint |
S3_BUCKET |
canary-uploads | Bucket name |
S3_ACCESS_KEY |
- | S3 access key |
S3_SECRET_KEY |
- | S3 secret key |
S3_REGION |
us-east-1 | S3 region |
S3_PUBLIC_URL |
- | Public URL for serving files |
Canary uses Gotenberg for PDF generation. Gotenberg is included in the Docker Compose setup.
- Design your email template using the visual editor
- Enable "Generate PDF" on the template or per-send request
- When sending, Canary renders the template to HTML
- Gotenberg converts the HTML to PDF
- PDF is attached to the email automatically
# Send email with PDF attachment
curl -X POST http://localhost:3001/api/v1/send \
-H "X-API-Key: cnry_your_key" \
-H "Content-Type: application/json" \
-d '{
"templateId": "invoice-template",
"to": "customer@example.com",
"subject": "Your Invoice",
"variables": {
"invoiceNumber": "INV-001",
"amount": "$100.00"
},
"generatePdf": true
}'curl http://localhost:3001/api/v1/{emailId}/status \
-H "X-API-Key: cnry_your_key"Response:
{
"success": true,
"data": {
"id": "email-uuid",
"status": "sent",
"sentAt": "2024-01-15T10:30:00Z",
"hasPdfAttachment": true
}
}canary/
├── apps/
│ ├── web/ # React frontend
│ │ └── src/
│ │ ├── components/ # Shared UI components
│ │ └── features/ # Feature modules
│ │ └── templates/email-builder/ # Visual editor
│ │
│ └── api/ # Fastify backend
│ └── src/
│ ├── modules/ # API routes & services
│ ├── jobs/ # Background job processing
│ │ ├── queues.ts # Queue definitions
│ │ ├── worker.ts # Worker startup
│ │ └── processors/ # Job handlers
│ ├── services/ # Shared services
│ │ └── pdf.service.ts # Gotenberg integration
│ ├── adapters/ # Email provider implementations
│ └── db/ # Drizzle schema & migrations
│
├── packages/
│ └── shared/ # Shared TypeScript types
│
├── docker/ # Docker configuration
├── docker-compose.yml # Development services
└── docker-compose.prod.yml # Production deployment
# Start all services (web + api)
pnpm dev
# Run only web
pnpm --filter @canary/web dev
# Run only api
pnpm --filter @canary/api dev
# Database commands
pnpm db:generate # Generate migrations from schema changes
pnpm db:migrate # Apply pending migrations
pnpm db:studio # Open Drizzle Studio (database GUI)
# Code quality
pnpm lint # Run linting
pnpm format # Format code with Prettier
pnpm build # Build all packages- Open MinIO Console at http://localhost:9001
- Login with
minioadmin/minioadmin - Create a bucket named
canary-uploads - Set bucket policy to public (for image serving)
This project is licensed under the GNU Affero General Public License v3.0.
See LICENSE for the full terms.
