Docs menu
Getting started
IntroductionInstallationProject structureBuilding pages
RoutingPagesLayoutsStylingMarkdownContent and dataIslandsGoing live
SEO and headDeployingFormsGuides
Build a brochure siteAdvanced
Running as a serverReference
ReferenceAdvanced
Running as a server
For sites that handle their own form posts. Most sites don't need this.
Most sites are static and use a form service. This page is for the exceptions: sites that want to receive form posts themselves.
Handling posts
A page that exports post receives its form posts:
export def post {form}
return {status: 422, values: form, errors: {email: 'Please enter your email.'}} unless form.email
await sendEnquiry(form) # your own code: Resend, Postmark, …
{redirect: '/contact/thanks'} # → 303 redirect
export default tag ContactPage
form = null # what `post` returned, when re-shown
<self>
<form method='post' action='/contact'>
<input type='email' name='email' value=(form..values..email or '')>
<small> form.errors.email if form..errors..email
<button type='submit'> "Send"
formholds the posted fields as strings; repeated fields (checkboxes) arrive as an array.- Return
{redirect}when it worked, or anything else to show the page again with it as theformprop.statussets the response code, e.g. 422 for validation errors. - Plain form posts and JSON are read, up to 1 MB. File uploads aren't supported yet (415).
Pages without
postanswer posts with 405. - Keep
requiredandtype='email'in the HTML too: the browser checks first, the server checks again.
Sending the email is your own code. With Resend it's one fetch:
export def sendEnquiry form
let res = await fetch('https://api.resend.com/emails',
method: 'POST'
headers: {'Authorization': "Bearer {process.env.RESEND_API_KEY}", 'Content-Type': 'application/json'}
body: JSON.stringify(from: 'Website <web@acme.example>', to: 'you@acme.example', reply_to: form.email, subject: 'New enquiry', text: form.message)
)
throw new Error("Email failed: {res.status}") unless res.ok
Running it
imba-pages start
compiles the site into .pages/build/ (a self-contained entry.js plus public/) and runs
it. To deploy, copy that folder to any host that runs Node (Render, Fly.io, Railway, a VPS)
and run node entry.js, with PORT and your keys as environment variables.
Posts also work under dev. A static build still renders the page, but the build prints a
note: nothing in dist/ can receive the post.
One page, both ways
mode says how the site is running: 'dev', 'static' or 'server'. A contact page can use
Netlify Forms in the static build and its own post elsewhere:
import {mode} from '@milktop/imba-pages'
const netlify = mode == 'static'
export const post = netlify ? undefined : handlePost