Wildcard email aliasing on Cloudflare Workers. Forward, filter, and burn addresses without exposing your inbox.
Veil is a self-hosted, open-source email alias relay built entirely on Cloudflare's developer platform. It consists of two Cloudflare Workers deployed from a single GitHub monorepo via Workers Builds:
- Email Worker: receives all inbound mail via Cloudflare Email Routing, enforces alias rules against a D1 database, and forwards allowed mail to your real inbox.
- Frontend Worker: an Astro SSR application that serves the alias management dashboard and exposes a REST API for alias CRUD operations.
- Cloudflare Access authentication - Secure login via CF Access with JWT verification
- Configurable settings - Set forwarding address and rejection message via dashboard
- Expiring aliases - Set optional expiration dates for temporary addresses
- Rate limiting - 100 emails/minute per sender to prevent abuse
- CSV export - Download your alias list for backup
- Pagination - Handles large alias lists efficiently
Veil uses Cloudflare's
message.forward()API, which reforwards mail with automatic SRS (Sender Rewriting Scheme) applied to the envelope sender. It is not a transparent SMTP proxy. SPF and bounce handling are managed automatically by Cloudflare. DMARC strict-alignment failures may occur with some senders, which is a known limitation shared by all forwarding-based alias services (SimpleLogin, AnonAddy, etc.).
- A Cloudflare account
- A domain added to Cloudflare
- Cloudflare Email Routing enabled on the domain
- Workers Paid plan (for D1 + Email Workers)
wranglerCLI installed locally (npm install -g wrangler)
To run the frontend locally:
cd workers/frontend
npm install
npm run devTo test the email worker locally, you'll need to use wrangler dev with email routing simulation. However, email workers are best tested in production since Cloudflare Email Routing cannot be fully simulated locally.
Click the Fork button in the top-right corner of this page.
Run locally (once):
wrangler d1 create veil-dbCopy the database_id from the output. Open both:
workers/email-worker/wrangler.tomlworkers/frontend/wrangler.toml
Replace REPLACE_WITH_YOUR_D1_ID with your actual ID, then commit and push the change to GitHub.
Run locally (once, from the repo root):
wrangler d1 execute veil-db --file=./schema.sqlIf you are upgrading from a previous version without the logs table, run:
wrangler d1 execute veil-db --command="CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER NOT NULL, level TEXT NOT NULL DEFAULT 'info', event TEXT NOT NULL, alias TEXT, from_addr TEXT, to_addr TEXT, message TEXT, details TEXT);"
wrangler d1 execute veil-db --command="CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON logs(timestamp DESC);"
wrangler d1 execute veil-db --command="CREATE INDEX IF NOT EXISTS idx_logs_level ON logs(level);"
wrangler d1 execute veil-db --command="CREATE INDEX IF NOT EXISTS idx_logs_event ON logs(event);"Workers Builds automatically deploys your workers when you push to GitHub. Configure each worker:
Email Worker (workers/email-worker)
- Go to Workers & Pages → Create → Connect to Git
- Select your fork and configure:
- Root directory:
workers/email-worker - Install command:
npm install - Build command: (leave empty)
- Deploy command:
wrangler deploy - Branch trigger:
main
- Root directory:
- Under Settings → Build configurations, add a build watch path:
- Include:
workers/email-worker/**
- Include:
This ensures the email worker only rebuilds when files in workers/email-worker/ change.
Frontend Worker (workers/frontend)
- Create another Workers Builds project for the same repo
- Configure:
- Root directory:
workers/frontend - Install command:
npm install - Build command:
npm run build - Deploy command:
wrangler deploy - Branch trigger:
main
- Root directory:
- Add a build watch path:
- Include:
workers/frontend/**
- Include:
The frontend's wrangler.toml includes an assets directive that ensures static files (CSS, JS, fonts) are uploaded alongside the worker. This is critical; without it, the dashboard will load as a blank page.
Both workers have Observability enabled via wrangler.toml, which automatically collects logs, errors, and invocation data. You can view logs in the dashboard under Worker → Observability.
In the Cloudflare dashboard, go to Worker → Settings → Variables and Secrets for each Worker.
Critical: Set ALL variables as Secret type. Variables set as "Text" or "JSON" will be overwritten on each deployment from GitHub. Only Secret type values persist across deployments since they are encrypted and not included in
wrangler.toml.
Email Worker Variables
| Variable | Type | Required | Description |
|---|---|---|---|
FORWARD_TO |
Secret | ❌ | Destination email address (required - must Settings page, Otherwise forwarding will fail)) |
REJECT_MESSAGE |
Secret | ❌ | SMTP rejection text (optional - defaults via Settings page) |
Frontend Worker Variables
| Variable | Type | Required | Description |
|---|---|---|---|
CF_ACCESS_TEAM_DOMAIN |
Secret | ✅ | Your CF Access team URL (e.g., https://yourteam.cloudflareaccess.com) |
CF_ACCESS_AUD |
Secret | ✅ | Your CF Access application audience tag |
DOMAIN |
Secret | ✅ | The relay domain e.g. yourdomain.com |
APP_NAME |
Secret | ❌ | Defaults to Veil |
APP_DESCRIPTION |
Secret | ❌ | Defaults to the tagline above |
ACCENT_COLOR |
Secret | ❌ | Hex color, defaults to #6d83f2 |
Note:
FORWARD_TOandREJECT_MESSAGEcan be configured via the Settings page in the dashboard. Settings stored in the database take precedence over environment variables.
Protect your frontend Worker with Cloudflare Access:
- Go to Zero Trust → Access → Applications
- Add an application for your frontend Worker URL
- Create an access policy (e.g., email domain, specific emails, or GitHub groups)
- Note the Application Audience (AUD) tag from the application settings
- Set the
CF_ACCESS_AUDvariable in your frontend Worker to this value - Set
CF_ACCESS_TEAM_DOMAINto your team URL (e.g.,https://yourteam.cloudflareaccess.com)
In the Cloudflare dashboard, go to Worker → Settings → Bindings → D1 Database for each Worker:
- Binding name:
DB - D1 Database:
veil-db
- Go to your domain → Email → Email Routing
- Add a Destination address - enter your
FORWARD_TOaddress and click Verify - Click the verification link sent to that address
- Add a Catch-All rule
- Action: Send to Worker
- Select:
veil-email-worker
Note: The destination address must be verified before email forwarding will work. You'll get a "destination address not verified" error if you skip this step.
After completing Steps 4-7, trigger your first deployment:
- Go to each Worker in the dashboard
- Click Deployments → you should see a pending/running build
- Wait for both workers to deploy successfully
- Visit your frontend Worker's URL (found in the dashboard under Preview or Triggers)
Both Workers will automatically rebuild and redeploy on every push to main, but only if files within their respective watch paths changed.
- Visit your frontend Worker URL. You should be prompted to authenticate via Cloudflare Access
- After authenticating, you should see the Veil dashboard
- Go to Settings and configure your forwarding address
- Send a test email to
test@yourdomain.com - Check the dashboard to see the alias appear
- Verify the email was forwarded to your configured address
- Visit your frontend Worker's URL to access the dashboard (requires CF Access authentication)
- Configure Forward To and Reject Message in the Settings page
- Aliases are created automatically when emails arrive (controlled by Cloudflare Email Routing rules)
- Click Disable to block a specific alias (mail is rejected at the Worker level)
- Click Delete to remove the record entirely
- Use Export CSV to backup your alias list
- Re-enable a disabled alias at any time with the Enable button
| Email Worker | Frontend Worker | |
|---|---|---|
| Root directory | workers/email-worker |
workers/frontend |
| Install command | npm install |
npm install |
| Build command | (none) | npm run build |
| Deploy command | wrangler deploy |
wrangler deploy |
| Output directory | (none) | dist |
| Branch trigger | main |
main |
| Build watch paths | workers/email-worker/** |
workers/frontend/** |
| D1 binding | DB → veil-db |
DB → veil-db |
MIT
