jmail Documentation
A free, self-hosted disposable email service that runs entirely on Cloudflare Workers. Receive email, store it in D1, and serve a clean UI from the edge.
1Getting Started
Deploy your own instance in five steps. You need a Cloudflare account and a domain with nameservers pointed at Cloudflare.
Prerequisites
- Cloudflare account (free tier is enough)
- A domain added to Cloudflare
- Node.js v18+ and npm
1. Clone and install
git clone https://github.com/josskixg/jmail.git
cd jmail
npm install
2. Login to Cloudflare
npx wrangler login
3. Configure wrangler.toml
Set your mail domains, web host, branding, and theme variables. See the Configuration section for the full list.
4. Create the D1 database
npx wrangler d1 create jmail-db
Copy the returned database_id into wrangler.toml, then apply the schema:
npx wrangler d1 execute jmail-db --remote --file=src/db/schema.sql
5. Deploy
npx wrangler deploy
2Configuration
All settings live in wrangler.toml under [vars]. Environment variables are read at runtime; redeploy after changes.
App settings
| Variable | Type | Default | Description |
|---|---|---|---|
APP_NAME | string | jmail | Display name shown in the UI |
MAIL_DOMAIN | string | atiku.web.id | Comma-separated list of allowed mail domains |
WEB_HOST | string | mail.atiku.web.id | Web frontend hostname |
TAGLINE | string | Free Disposable Temporary Email & Anonymous Inbox | Tagline below the app name |
FOOTER_TEXT | string | CREATED BY JMail | Footer attribution text |
META_KEYWORDS | string | jmail, temp mail, ... | Content for <meta name="keywords"> |
META_AUTHOR | string | JMAIL | Content for <meta name="author"> |
Ads variables
| Variable | Type | Default | Description |
|---|---|---|---|
ADS_ENABLED | string | false | Set true to activate ad slots |
ADS_CLIENT | string | | AdSense publisher ID (e.g. ca-pub-1234567890) |
ADS_SLOT_HEADER | string | | AdSense slot ID for header placement |
ADS_SLOT_INLIST | string | | AdSense slot ID between inbox rows |
ADS_SLOT_FOOTER | string | | AdSense slot ID for footer placement |
ADS_CUSTOM_HEADER | string | | Custom HTML/JS for header ad, overrides AdSense slot |
ADS_CUSTOM_INLIST | string | | Custom HTML/JS for in-list ad, overrides AdSense slot |
ADS_CUSTOM_FOOTER | string | | Custom HTML/JS for footer ad, overrides AdSense slot |
Theme variables
Leave empty to keep the default theme. Each value maps to a CSS custom property.
| Variable | CSS Property | Controls |
|---|---|---|
THEME_BG | --bg-canvas | Background color |
THEME_PRIMARY | --pink-flower | Primary brand color |
THEME_PRIMARY_SOFT | --pink-soft | Primary lighter shade |
THEME_ACCENT | --yellow-flower | Accent / secondary color |
THEME_ACCENT_SOFT | --yellow-soft | Accent lighter shade |
#2563eb33.
3API Reference
All endpoints live under /api/. Base URL: https://mail.atiku.web.id/api/
Sessions are anonymous. Pass an optional x-session-id header to group and persist inboxes in the browser.
● App configuration
curl -s https://mail.atiku.web.id/api/config | jq
Response 200 OK:
{
"appName": "jmail",
"mailDomain": "atiku.web.id",
"mailDomains": ["atiku.web.id", "lalekne.web.id"],
"webHost": "mail.atiku.web.id",
"tagline": "Free Disposable Temporary Email & Anonymous Inbox",
"footerText": "CREATED BY JMail",
"ads": {
"enabled": false,
"client": "",
"slots": { "header": "", "inlist": "", "footer": "" },
"custom": { "header": "", "inlist": "", "footer": "" }
},
"theme": {}
}
theme contains any CSS variable overrides (e.g. {"--bg-canvas": "#0a0a0f"}). ads only lists enabled slots.
● Create or retrieve a session
x-session-id to create a new session, or pass one to reuse it.
# New session
curl -s https://mail.atiku.web.id/api/session
# Reuse an existing session
curl -s https://mail.atiku.web.id/api/session \
-H "x-session-id: 550e8400-e29b-41d4-a716-446655440000"
Response 200 OK: {"sessionId": "550e8400-e29b-41d4-a716-446655440000"}
● List inboxes
curl -s https://mail.atiku.web.id/api/inboxes \
-H "x-session-id: 550e8400-e29b-41d4-a716-446655440000" | jq
Response 200 OK:
[
{ "address": "kopihujan23@atiku.web.id", "created_at": "2026-06-26 07:48:19" }
]
Errors: 400 Missing x-session-id
● Create an inbox
# Random address
curl -s -X POST https://mail.atiku.web.id/api/inboxes \
-H "x-session-id: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{}'
# Custom local-part on a specific domain
curl -s -X POST https://mail.atiku.web.id/api/inboxes \
-H "x-session-id: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{"localPart": "test", "domain": "lalekne.web.id"}'
Response 201 Created:
{ "address": "langitbiru23@atiku.web.id", "created_at": "2026-06-26 07:48:19" }
Errors: 400 Missing x-session-id, 400 Invalid domain: ... (domain must be in mailDomains)
● Delete an inbox from session
# Address must be URI-encoded: test123%40atiku.web.id
curl -s -X DELETE "https://mail.atiku.web.id/api/inboxes/test123%40atiku.web.id" \
-H "x-session-id: 550e8400-e29b-41d4-a716-446655440000"
Response 200 OK: {"ok": true}
● Fetch messages
curl -s "https://mail.atiku.web.id/api/inboxes/test123%40atiku.web.id/messages" \
-H "x-session-id: 550e8400-e29b-41d4-a716-446655440000" | jq
Response 200 OK:
[
{
"id": "msg_1782461413912_0956a83c",
"inbox_address": "test123@atiku.web.id",
"from_address": "someone@gmail.com",
"subject": "Hello",
"body": "This is the email body",
"received_at": "2026-06-26 08:10:14"
}
]
Errors: 400 Missing x-session-id, 403 Inbox not in this session
● Public stats
curl -s https://mail.atiku.web.id/api/stats | jq
Response 200 OK:
{
"status": "ok",
"appName": "jmail",
"author": "JMAIL",
"totalInboxes": 128,
"totalMessages": 543,
"domains": ["atiku.web.id", "lalekne.web.id"],
"timestamp": "2026-08-02T00:00:00.000Z"
}
● Health check
curl -s https://mail.atiku.web.id/api/health | jq
Response 200 OK:
{
"status": "healthy",
"service": "jmail",
"version": "1.0.0",
"timestamp": "2026-08-02T00:00:00.000Z"
}
4Theme Customization
jmail's UI is driven by five CSS custom properties. Override any of them with THEME_* env vars in wrangler.toml; empty values keep the default theme.
Cyberpunk
THEME_BG = "#0a0a0f"
THEME_PRIMARY = "#ff2d95"
THEME_PRIMARY_SOFT = "#ff2d9533"
THEME_ACCENT = "#00f0ff"
THEME_ACCENT_SOFT = "#00f0ff33"
Forest
THEME_BG = "#0f1a0f"
THEME_PRIMARY = "#2d8a4e"
THEME_PRIMARY_SOFT = "#2d8a4e33"
THEME_ACCENT = "#c4a35a"
THEME_ACCENT_SOFT = "#c4a35a33"
GET /api/config under theme, so no redeploy of assets is needed, only of the Worker config.
5Ads Setup
Ads are off by default. Two modes: Google AdSense or custom HTML. Three placements: header, in-list, and footer.
AdSense
ADS_ENABLED = "true"
ADS_CLIENT = "ca-pub-1234567890"
ADS_SLOT_HEADER = "1234567890"
ADS_SLOT_INLIST = "1234567891"
ADS_SLOT_FOOTER = "1234567892"
A slot only renders when both ADS_CLIENT and its slot ID are set.
Custom HTML
Any placement can use raw HTML/JS instead. Custom content takes precedence over AdSense for the same slot.
ADS_ENABLED = "true"
ADS_CUSTOM_FOOTER = "<!-- your ad tag --><script>...</script>"
6Email Routing
Inbound email flows through Cloudflare Email Routing. Set it up once per domain and the Worker handles the rest.
1. Enable Email Routing
- Cloudflare Dashboard, then your domain, then Email, then Email Routing
- Click Get started and enable routing. MX records are created automatically.
2. Route all mail to the Worker
- Go to Routing rules, then Catch-all
- Set action to Send to a Worker
- Select the jmail Worker
3. Verify the Worker binding
In wrangler.toml, the email handler is enabled with:
[email]
action = "process"
4. Add SPF (recommended)
| Type | Name | Content |
|---|---|---|
TXT | @ | v=spf1 include:_spf.mx.cloudflare.net ~all |
Verify
Send a test email to any address at your domain and open it in the web UI. It should appear within seconds (the UI auto-syncs every 5s).
7Deployment
Production deploy
npm run deploy
Equivalent to npx wrangler deploy. Deploys the Worker plus static assets to the edge.
Database migration
npm run db:migrate # apply schema to production D1
npm run db:local # apply schema to local D1 (dev)
Local development
npx wrangler dev # run Worker locally with D1 + assets
Live logs
npx wrangler tail --format pretty
Custom domain
Add a route with custom_domain = true and set workers_dev = false to disable the *.workers.dev URL:
[[routes]]
pattern = "mail.yourdomain.com"
custom_domain = true
Troubleshooting
| Problem | Fix |
|---|---|
| Emails not arriving | Confirm the catch-all route targets the Worker and MX records exist |
| Inbox stays empty | Claim the exact address: New, then type local-part, then Create |
DNS_PROBE_FINISHED_NXDOMAIN | Nameservers not pointed at Cloudflare; wait for propagation |
| Wrangler version warning | This project needs Wrangler v4: npm install --save-dev wrangler@4 |