A universal form submission service for all your websites. No more paying for form services!
- Sign up at resend.com (free tier available)
- Create an API key
In your Val Town project settings, add these environment variables:
RESEND_API_KEY- Your Resend API key (required)OWNER_EMAIL- Your email address where notifications will be sent (required)FROM_EMAIL- Sender email address (optional, defaults toonboarding@resend.dev)
Set the HTTP trigger on backend/index.ts and you're good to go!
Edit config.json to route form submissions and set default auto-responders per domain/page:
{ "example.com/contact": { "recipient": "contact@example.com", "autorespond": { "message": "Thanks for contacting us! We'll get back to you within 24 hours.", "from": "noreply@example.com" } }, "client1.com/": { "recipient": "client1@example.com", "autorespond": { "message": "Thank you for your message!", "from": "hello@client1.com" } }, "mysite.com/careers": { "recipient": "hr@mysite.com", "autorespond": "Thanks for your interest! We'll review your application soon." } }
Note: autorespond can be either a string (uses default FROM_EMAIL) or an object with message and from fields.
Portal Token: Each domain gets a unique portal_token (e.g., "client-abc123"). This creates a client portal at /portal/client-abc123 where your client can:
- View all their form submissions
- See current settings (recipient, autoresponder)
- (Coming soon) Update settings themselves
How it works:
- Exact match first:
domain.com/pageβ specific recipient and autorespond - Falls back to domain:
domain.comβ domain recipient and autorespond - Falls back to
OWNER_EMAILif no match - Forms can override autorespond with
_autorespondfield
Each domain in your config.json gets a unique portal URL that you can share with clients:
https://yourval.web.val.run/portal/sfl-abc123
Clients can:
- β View all submissions to their site
- β See auto-updated submission count
- β³ Update recipient emails (coming soon)
- β³ Customize auto-responder message (coming soon)
Security Note: Portal URLs use random tokens. Keep them private - anyone with the URL can view submissions.
If you want emails to come from your own domain instead of onboarding@resend.dev:
- Verify your domain in Resend
- Set
FROM_EMAILenvironment variable tonoreply@yourdomain.com
- β One endpoint for all your sites
- β Auto-organizes by domain and page
- β Email notifications on new submissions
- β Error notifications sent to owner
- β Custom auto-responder per form
- β Admin dashboard to view submissions
- β Spam protection (honeypot + rate limiting)
- β Dynamic field support
- β Per-domain email routing via config file
<form id="contact-form"> <input type="email" name="email" required> <input type="text" name="name" required> <textarea name="message" required></textarea> <button type="submit">Send</button> </form> <script> document.getElementById('contact-form').addEventListener('submit', async (e) => { e.preventDefault() const formData = new FormData(e.target) const data = Object.fromEntries(formData) // Add auto-responder message data._autorespond = 'Thanks for reaching out! I\'ll get back to you soon.' const response = await fetch('YOUR_VAL_URL/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) if (response.ok) { alert('Message sent!') e.target.reset() } }) </script>
axios.post('YOUR_VAL_URL/submit', {
email: 'user@example.com',
name: 'John Doe',
message: 'Hello!',
_autorespond: 'Thanks! Will get back to you in a jiffy.'
})
<script> import FormSubmission from 'https://yourval.web.val.run/frontend/FormSubmission.svelte' </script> <!-- Basic (contact preset: name, email, message) --> <FormSubmission service_url="https://yourval.web.val.run" /> <!-- Custom fields --> <FormSubmission service_url="https://yourval.web.val.run" fields={[ { name: 'name', label: 'Name', type: 'text', required: true }, { name: 'email', label: 'Email', type: 'email', required: true }, { name: 'phone', label: 'Phone', type: 'tel', required: false }, { name: 'message', label: 'Message', type: 'textarea', required: true, rows: 5 } ]} /> <!-- Fully themed --> <div style=" --button-bg: #10b981; --button-hover-bg: #059669; --input-border-radius: 8px; --form-max-width: 600px; "> <FormSubmission service_url="https://yourval.web.val.run" /> </div>
Available CSS Variables:
--form-max-width- Form container width (default: 500px)--form-font-family- Font family--form-group-spacing- Space between fields (default: 1.5rem)--label-color- Label text color--label-font-size- Label size--label-font-weight- Label weight--input-padding- Input padding--input-border- Input border--input-border-radius- Input corner radius--input-bg- Input background--input-color- Input text color--input-focus-border-color- Border color on focus--input-focus-shadow- Shadow on focus--button-bg- Button background--button-hover-bg- Button hover background--button-color- Button text color--button-padding- Button padding--button-border-radius- Button corner radius--success-bg/--success-color- Success message colors--error-bg/--error-color- Error message colors
POST /submit- Submit a formGET /admin- View all submissions (admin dashboard)GET /portal/:token- Client portal (unique per domain)GET /api/submissions- Get submissions as JSONGET /api/portal/:token/submissions- Get submissions for specific portal
email- User's email (required for auto-responder)_autorespond- Custom message to send back to the user_honeypotorhoneypot- Spam trap field (leave empty, hide with CSS)
Add a honeypot field to your forms:
<input type="text" name="_honeypot" style="display:none" tabindex="-1" autocomplete="off">
Rate limiting: 5 submissions per minute per IP
Submissions are automatically organized by:
- Domain - Extracted from the referer/origin header
- Page - The specific page path where the form was submitted
- Timestamp - When the submission was received
- Export to Google Sheets
- Delete submissions
- Domain whitelist
- Custom email templates
- Webhook integrations