A powerful Cloudflare Worker that handles HTML form submissions and forwards them via Mailgun to your email address. Supports single and multiple forms with advanced configuration options.
- β Mailgun email integration - Reliable email delivery
- β CORS support - Works with JavaScript clients
- β Honeypot protection - Built-in bot protection
- β Multiple forms - Support for different form types with file-based configuration
- β Multiple recipients - Send emails to different addresses per form
- β Custom redirects - Custom thank you pages per form
- β File-based configuration - Easy form management through JSON files
- β Rate limiting - Optional IP-based rate limiting (KV storage)
- β Turnstile verification - Optional Cloudflare Turnstile integration
- β D1 database storage - Optional form submission storage
- β Local testing - Full local development support
- Cloudflare Account with Workers enabled
- Mailgun Account with API key and domain configured
- Domain Setup (optional) - Point your domain to Cloudflare
npm install- Go to your Mailgun dashboard
- Add your domain (e.g.,
mail.yourdomain.com) - Get your API key from Settings > API Keys
- Verify your domain in Mailgun
The worker uses file-based configuration instead of environment variables for form settings:
- Edit
config/defaults.json- Set default settings:
{
"defaultNotifyTo": "your-email@example.com",
"defaultFromEmail": "forms@mail.yourdomain.com",
"defaultThankYouUrl": "/thank-you"
}- Edit
config/forms.json- Configure individual forms:
{
"contact": {
"name": "Contact Form",
"notifyTo": ["your-email@example.com"],
"fromEmail": "forms@mail.yourdomain.com",
"thankYouUrl": "/thank-you",
"subject": "New Contact Form Submission",
"enabled": true
}
}Set the required secrets using Wrangler:
# Your Mailgun API key
wrangler secret put MAILGUN_API_KEY
# Your Mailgun domain (e.g., "mail.yourdomain.com")
wrangler secret put MAILGUN_DOMAIN# Deploy to production
npm run deploy
# Or deploy a preview
npm run deploy:preview<form action="https://your-worker.workers.dev/submit" method="POST">
<input type="text" name="name" placeholder="Your Name" required>
<input type="email" name="email" placeholder="Your Email" required>
<textarea name="message" placeholder="Your Message" required></textarea>
<!-- Honeypot field (hidden from users) -->
<input type="text" name="_hp" style="display:none" tabindex="-1">
<button type="submit">Send Message</button>
</form>The worker supports multiple forms with different configurations through file-based settings.
- Default form:
https://your-worker.workers.dev/submit - Named forms:
https://your-worker.workers.dev/{form-name}/submit
https://your-worker.workers.dev/contact/submithttps://your-worker.workers.dev/support/submithttps://your-worker.workers.dev/newsletter/submit
type FormConfig = {
name: string; // Display name for the form
notifyTo: string[]; // Array of email addresses to notify
fromEmail?: string; // From email address (uses default if not specified)
thankYouUrl?: string; // Custom thank you page URL
subject?: string; // Custom email subject
enabled?: boolean; // Enable/disable the form (default: true)
}If a form doesn't have specific configuration:
- Recipients: Uses
defaultNotifyTofromconfig/defaults.json - From Email: Uses
defaultFromEmailfromconfig/defaults.json - Subject: Uses
"New {form-name} submission" - Thank You: Uses
defaultThankYouUrlfromconfig/defaults.jsonor form's_redirectfield
-
Install dependencies:
npm install
-
Set up environment variables:
- Edit
.dev.varsfile with your Mailgun credentials - Add your API key and domain
- Edit
-
Start the development server:
npm run dev
-
Test the form:
- Open
test/test.htmlin your browser - Fill out and submit the form
- Check your email for the submission
- Open
Make sure your .dev.vars file contains:
MAILGUN_API_KEY=key-your-actual-mailgun-api-key
MAILGUN_DOMAIN=mail.yourdomain.comIf you want to enable rate limiting (5 submissions per 10 minutes per IP):
- Create a KV namespace in Cloudflare dashboard
- Update
wrangler.tomlwith your KV namespace ID:
[[kv_namespaces]]
binding = "RATE_KV"
id = "your-kv-namespace-id"If you want to store form submissions in a database:
- Create a D1 database in Cloudflare dashboard
- Update
wrangler.tomlwith your database details:
[[d1_databases]]
binding = "FORMS_D1"
database_name = "forms"
database_id = "your-database-id"_form: Form identifier (default: "contact")_redirect: Custom redirect URL after submission_hp: Honeypot field (leave empty, hidden from users)
The worker uses two configuration files:
Contains default settings used as fallbacks:
{
"defaultNotifyTo": "your-email@example.com",
"defaultFromEmail": "forms@mail.yourdomain.com",
"defaultThankYouUrl": "/thank-you"
}Contains individual form configurations:
{
"contact": {
"name": "Contact Form",
"notifyTo": ["your-email@example.com"],
"fromEmail": "forms@mail.yourdomain.com",
"thankYouUrl": "/thank-you",
"subject": "New Contact Form Submission",
"enabled": true
},
"support": {
"name": "Support Form",
"notifyTo": ["support@yourdomain.com"],
"subject": "New Support Request",
"enabled": true
}
}Form submissions are sent as JSON in the email body:
{
"name": "John Doe",
"email": "john@example.com",
"message": "Hello!",
"_form": "contact"
}View logs in real-time:
wrangler tailExample log output:
[2025-01-09T23:30:00.000Z] POST /contact/submit - IP: 192.168.1.1
Found form config for: contact
π§ Sending email via Mailgun:
From: forms@mail.yourdomain.com
To: your-email@example.com
Subject: New Contact Form Submission
β
Email sent successfully to: your-email@example.com
π Redirecting to: /thank-you
The Wrangler dev server will show console.log output in your terminal. Look for:
- Form data being received
- Mailgun API responses
- Any error messages
In your browser's developer tools:
- Go to Network tab
- Submit the form
- Check the request/response details
- Verify the email arrives in your inbox
- Check spam folder if needed
- Verify the email content and formatting
"Connection Error" in Browser:
- Make sure Wrangler dev server is running
- Check that it's running on the correct port (8787)
- Try refreshing the page
"Mailgun failed" Error:
- Check your API key in
.dev.vars - Verify your Mailgun domain is correct
- Ensure your Mailgun account is active
Email Not Received:
- Check spam folder
- Verify the email addresses in
config/defaults.jsonorconfig/forms.json - Check Mailgun logs in your dashboard
Form Not Working:
- Check if form is enabled in configuration
- Verify the route matches your configuration key
- Check logs with
wrangler tail
Emails Not Sending:
- Verify Mailgun configuration
- Check recipient email addresses in
config/forms.jsonandconfig/defaults.json - Ensure form configuration files contain valid JSON
Wrong Redirect:
- Check
thankYouUrlin form configuration - Verify form's
_redirectfield - Check URL query parameters
Before deploying to production:
- Test with
npm run dev:remoteto use Cloudflare's edge - Set up your production secrets:
wrangler secret put MAILGUN_API_KEY wrangler secret put MAILGUN_DOMAIN # Form configurations are now in config/forms.json and config/defaults.json - Deploy to a preview environment first:
npm run deploy:preview
- Test the preview URL
- Deploy to production:
npm run deploy
- Honeypot: Hidden
_hpfield catches bots - Rate Limiting: Optional IP-based rate limiting
- Turnstile: Optional Cloudflare Turnstile verification
- Input Validation: Basic validation and sanitization
- Environment Variables: Encrypted and secure
- Use different
_formvalues to test multiple form types - Test with both HTML forms and JavaScript fetch requests
- Verify CORS works if you plan to use it from different domains
- Test the redirect functionality thoroughly
- Check that honeypot protection works as expected
- Use
wrangler tailto monitor form submissions in real-time
The worker can be customized by modifying src/worker.ts:
- Change email format
- Add custom validation
- Modify redirect behavior
- Add additional security measures
- Customize form processing logic
- Domain not working: Ensure your domain is configured in Cloudflare DNS
- Email not sending: Check Mailgun API key and domain configuration
- CORS issues: The worker includes CORS headers for JavaScript clients
- Rate limiting: Check KV namespace configuration if enabled
- Multi-form issues: Verify
FORM_CONFIGSenvironment variable is valid JSON