Form Handlers and Mailers
CGI form handlers: how contact forms and email mailers work server-side
How does a CGI form handler send contact form submissions by email?
A CGI form handler is a server-side script, typically written in Perl or Python, that receives POST data from an HTML form, validates and sanitizes the input, and then calls the server's mail transfer agent (sendmail or an SMTP library) to send the submission to a configured address. The script runs on the web server rather than in the browser, so the recipient email address is never exposed in the page source.
What a CGI form handler actually does
When a visitor submits an HTML form with method='POST' and an action attribute pointing to a CGI script, the web server passes the form data to that script through environment variables and standard input. The script reads and decodes the data (key=value pairs, URL-encoded), validates each field, and decides what to do next, typically sending an email and returning a thank-you page.
The mail step is where the choice matters. On older shared hosts the script calls sendmail directly by opening a pipe to /usr/sbin/sendmail. On modern setups it is cleaner to use an SMTP library (Net::SMTP in Perl, smtplib in Python) and authenticate with your mail service's credentials, which gives you proper delivery receipts and avoids being treated as a spam source by the receiving server.
The minimum a form handler must do
At bare minimum, every form handler must read the POST body, URL-decode each field, strip or escape HTML special characters before echoing anything back to the browser, validate that required fields are present, and validate that email addresses look like email addresses before passing them to any mail function. Skipping any of these steps opens the door to cross-site scripting (echoing raw user input) or email header injection (appending extra To/Cc lines via a crafted Name field).
Header injection is the most commonly overlooked risk. If you build the email headers by concatenating user input directly, an attacker can stuff newlines into the Name field and add their own recipients, turning your contact form into a spam relay. The fix is to strip newline characters from every field you put into email headers and to use a mail library that handles header construction for you rather than raw string concatenation.
Spam prevention without third-party scripts
Three techniques work well without loading any external JavaScript. First, a honeypot field: add a hidden text input with a name bots expect to fill (like 'website' or 'url'), keep it invisible with CSS, and reject any submission where that field is non-empty. Bots fill every visible and hidden field; real humans leave hidden ones blank.
Second, a time gate: record the time the form page was loaded (in a hidden field or server-side session), and reject submissions that arrive faster than a human could read and fill the form, typically under three to five seconds. Third, for high-volume forms, a server-side CAPTCHA or math challenge avoids the JavaScript dependency of reCAPTCHA while still filtering most automated submissions.
Choosing between Perl, Python, and CGI.pm
Perl with CGI.pm was the dominant choice for contact mailers throughout the 1990s and 2000s and still runs on nearly every shared host that supports CGI. CGI.pm handles parameter parsing, header generation, and HTML output escaping for you. Its form-handling code is terse and well-understood by a generation of tutorials. The downside is that CGI.pm is no longer maintained as aggressively, and Perl's CPAN dependency chain can be hard to manage on shared hosts where you cannot install modules.
Python's cgi module (standard library) gives you a similar level of convenience, and smtplib plus email.mime make the mail step clean. Python 3 is the current version; be aware that some older shared hosts still default to Python 2 in the CGI path, so confirm the interpreter version before writing Python 3 syntax. For either language, the actual form-handler code is small, typically fifty to a hundred lines for a complete, validated, spam-resistant contact mailer.
When a hosted form service makes more sense
CGI form handlers are worth self-hosting when you control the server, need the submission data in a specific format, or want to avoid third-party data processors entirely. They become a liability when your host restricts outbound SMTP, when sendmail is not available, or when you cannot keep the Perl or Python interpreter version up to date. In those cases a hosted form service (Formspree, Netlify Forms, or a simple AWS SES endpoint) offloads the mail delivery problem and handles spam filtering, while your HTML form simply points at their endpoint. The trade-off is that submission data passes through a third party.
The right choice depends on your hosting environment and your tolerance for maintenance. If you are on a modern VPS where you can install any package, self-hosted CGI gives you full control. If you are on a locked-down shared host where you cannot install CPAN modules or configure SMTP credentials, a hosted form endpoint is more reliable and takes less ongoing upkeep.
What to know
Key points
- Validate and sanitize before using any input. Strip HTML characters before echoing back; strip newlines from header fields to prevent injection.
- Never expose the recipient email in page source. The whole point of a server-side handler is that the destination address stays on the server.
- Use an SMTP library over sendmail where possible. Authenticated SMTP via Net::SMTP or smtplib gives you delivery receipts and avoids spam flags.
- Honeypot plus time gate catches most bots. Two no-JavaScript techniques that work together to filter automated submissions.
- Watch for header injection in name fields. Strip newline characters from any input you put into email headers; better, use a library that builds headers for you.
- Confirm the Python version on your host. Some shared hosts still run Python 2 in the CGI path; check before writing Python 3 syntax.
Get help
Listings and a local agent, when you are ready
Need a CGI script built, debugged, or a hosting environment configured? Forms use a clearly-marked placeholder endpoint until wired to a real system.
Reserved for an affiliate link to a vetted shared or VPS hosting provider that supports CGI and Perl/Python execution. Operator wires affiliate link here.
Self-hosted lead form for custom CGI development inquiries. Placeholder endpoint until wired to the operator's CRM or email handler.
Open inquiry form →Custom CGI development
Hosting setup help
Questions