A lightweight, self-hosted form submission handler that sends form data via email using Google SMTP. Perfect for static websites that need contact forms without relying on third-party services.
- π Easy to deploy with Docker
- π§ Send form submissions via Gmail SMTP
- π Domain whitelisting for CORS protection
- π― Simple REST API
- π¦ Minimal dependencies
- β Production-ready with health checks
- Docker and Docker Compose installed
- A Gmail account with 2-Factor Authentication enabled
- Google App Password (see setup instructions below)
git clone git@github.com:raditotev/simple-form-handler.git
cd simple-form-handler- Go to your Google Account
- Navigate to Security β 2-Step Verification
- Go to https://myaccount.google.com/apppasswords
- Enter app name and submit
- Copy the 16-character password (you won't see it again)
Edit docker-compose.yml and update:
environment:
- GMAIL_USER=youremail@gmail.com # Your Gmail address
- GMAIL_APP_PASSWORD=abcd efgh ijkl mnop # Google App Password
- RECIPIENT_EMAIL=recipient@example.com # Where to send submissions
- EMAIL_SUBJECT=New Form Submission # Email subject line
- ALLOWED_DOMAINS=https://yoursite.com,https://www.yoursite.comALLOWED_DOMAINS: Comma-separated list of domains allowed to submit forms. Include both www and non-www versions if needed.
# Build the Docker image
docker compose build
# Start the service
docker compose up -d
# Check logs
docker compose logs -fOpen your browser and visit:
http://localhost:3000/health
You should see:
{
"status": "ok",
"allowedDomains": ["https://yourdomain.com", "http://localhost:3000"]
}<!DOCTYPE html>
<html>
<head>
<title>Contact Form</title>
</head>
<body>
<form id="contactForm">
<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>
<button type="submit">Send</button>
</form>
<div id="status"></div>
<script>
document
.getElementById('contactForm')
.addEventListener('submit', async (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const data = Object.fromEntries(formData)
try {
const response = await fetch('http://localhost:3000/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const result = await response.json()
if (result.success) {
document.getElementById('status').innerHTML =
'<p style="color: green;">Message sent successfully!</p>'
e.target.reset()
} else {
document.getElementById('status').innerHTML =
'<p style="color: red;">Failed to send message.</p>'
}
} catch (error) {
document.getElementById('status').innerHTML =
'<p style="color: red;">Error: ' + error.message + '</p>'
}
})
</script>
</body>
</html>const handleSubmit = async (formData) => {
try {
const response = await fetch('https://your-form-handler.com/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
})
const result = await response.json()
if (result.success) {
console.log('Form submitted successfully!')
}
} catch (error) {
console.error('Submission error:', error)
}
}Submit form data to be emailed.
Request:
{
"name": "John Doe",
"email": "john@example.com",
"message": "Hello, this is a test message"
}Response (Success):
{
"success": true,
"message": "Form submitted successfully!"
}Response (Error):
{
"success": false,
"message": "Failed to send email"
}Check service status and view allowed domains.
Response:
{
"status": "ok",
"allowedDomains": ["https://yourdomain.com"]
}| Variable | Required | Description | Example |
|---|---|---|---|
GMAIL_USER |
Yes | Your Gmail address | your-email@gmail.com |
GMAIL_APP_PASSWORD |
Yes | Google App Password | abcd efgh ijkl mnop |
RECIPIENT_EMAIL |
Yes | Email to receive submissions | contact@example.com |
EMAIL_SUBJECT |
No | Subject line for emails | New Form Submission |
ALLOWED_DOMAINS |
Yes | Comma-separated allowed domains | https://example.com,https://www.example.com |
PORT |
No | Port to run on (default: 3000) | 3000 |
Only domains listed in ALLOWED_DOMAINS can submit forms. This prevents unauthorized use of your form handler.
Example configurations:
# Single domain
- ALLOWED_DOMAINS=https://mysite.com
# Multiple domains
- ALLOWED_DOMAINS=https://mysite.com,https://www.mysite.com,https://app.mysite.com
# Development and production
- ALLOWED_DOMAINS=https://mysite.com,http://localhost:3000,http://localhost:8080Important: Include the protocol (http:// or https://) and don't add trailing slashes.
# Start the service
docker compose up -d
# Stop the service
docker compose down
# View logs
docker compose logs -f form-handler
# Restart the service
docker compose restart
# Rebuild after code changes
docker compose build --no-cache
docker compose up -d
# Remove everything including volumes
docker compose down -vMIT
For issues, questions, or contributions, please open an issue on the project repository.
- Initial release
- Gmail SMTP support
- Domain whitelisting
- Docker deployment
- Health check endpoint