A generic WWW form to email gateway that parses the results of any HTML form and sends them to specified email addresses.
The original FormMail script has known security vulnerabilities (CVE-2001-0357, CVE-2002-0564) that have been exploited by spammers for over two decades. FormMail was listed as the #3 top attack vector in Q1 2002 by SecurityFocus.
Recommendation: Use modern alternatives like NMS FormMail, Tectite FormMail, or third-party services.
FormMail was one of the most popular CGI scripts from the 1990s, created in 1995. It is claimed to be the most popular CGI script on the World Wide Web, with over 2 million downloads since 1997.
"The nms versions are highly recommended for learning CGI programming - they use modern Perl best practices like strict, taint checking, and CGI.pm."
These alternatives offer FormMail-compatible functionality with proper security:
Drop-in replacement by London Perl Mongers. Uses strict, taint checking, CGI.pm. Recommended for production use.
Free Perl23-year track record with only 4 minor security flaws. PHP version available. Professional support.
Free PHP/PerlForm backend for static sites. Just point your form action to their URL.
Free 50/mo From $8/moBuilt into Netlify hosting. Just add netlify attribute to your form.
| Feature | Original FormMail | NMS/Tectite | Third-Party |
|---|---|---|---|
| Header injection protection | No | Yes | Yes |
| CSRF protection | No | Optional | Yes |
| Rate limiting | No | Optional | Yes |
| Spam filtering | No | Basic | AI-powered |
| CAPTCHA support | No | Yes | Yes |
| Server required | Yes | Yes | No |
FormMail is a generic WWW form to email gateway, which parses the results of any form and sends them to the specified user. This script has many formatting and operational options, most of which can be specified through the form itself.
| File | Description |
|---|---|
FormMail.pl |
The Perl script that processes form data and sends emails |
README |
Installation instructions and configuration guide |
| Field Name | Required | Description |
|---|---|---|
recipient |
Yes | Email address(es) to receive form data |
subject |
No | Subject line for the email |
redirect |
No | URL to redirect after successful submission |
required |
No | Comma-separated list of required fields |
env_report |
No | Environment variables to include in email |
sort |
No | Order of fields in email (order, alphabetic) |
print_config |
No | Config fields to include in email |
print_blank_fields |
No | Include empty fields in output |
title |
No | Title for success page |
return_link_url |
No | URL for return link on success page |
return_link_title |
No | Text for return link |
Download the FormMail package and extract it.
Edit FormMail.pl and configure these critical variables:
# IMPORTANT: Set your domain
@referers = ('yourdomain.com', 'www.yourdomain.com');
# IMPORTANT: Whitelist allowed recipients
@recipients = ('[email protected]', '[email protected]');
# Path to sendmail
$mailprog = '/usr/sbin/sendmail';
Upload FormMail.pl to your CGI-bin directory.
chmod 755 FormMail.pl
Point your HTML form to the script:
<form action="/cgi-bin/FormMail.pl" method="POST">
<input type="hidden" name="recipient" value="[email protected]">
<input type="hidden" name="subject" value="Contact Form">
<!-- Your form fields here -->
</form>
| Variable | Description | Example |
|---|---|---|
@referers |
Allowed domains that can use the script | ('yourdomain.com') |
@recipients |
Allowed email recipients (security) | ('[email protected]') |
$mailprog |
Path to sendmail program | '/usr/sbin/sendmail' |
Modern implementations of form-to-email functionality:
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use Email::Valid;
use HTML::Entities;
my $cgi = CGI->new;
# Configuration - SECURITY: Whitelist recipients
my @allowed_recipients = ('[email protected]');
my @allowed_referers = ('yourdomain.com');
# Validate referer
my $referer = $ENV{'HTTP_REFERER'} || '';
my $valid_referer = 0;
for my $allowed (@allowed_referers) {
$valid_referer = 1 if $referer =~ /$allowed/i;
}
die "Invalid referer" unless $valid_referer;
# Get form data
my $recipient = $cgi->param('recipient') || '';
my $subject = $cgi->param('subject') || 'Form Submission';
my $redirect = $cgi->param('redirect') || '';
# Validate recipient against whitelist
my $valid_recipient = 0;
for my $allowed (@allowed_recipients) {
$valid_recipient = 1 if lc($recipient) eq lc($allowed);
}
die "Invalid recipient" unless $valid_recipient;
# Validate email format
die "Invalid email" unless Email::Valid->address($recipient);
# Build email body
my $body = "Form submission received:\n\n";
for my $param ($cgi->param) {
next if $param =~ /^(recipient|subject|redirect)$/;
my $value = encode_entities($cgi->param($param));
$body .= "$param: $value\n";
}
# Add environment info
$body .= "\n--- Environment ---\n";
$body .= "Remote IP: $ENV{'REMOTE_ADDR'}\n";
$body .= "User Agent: $ENV{'HTTP_USER_AGENT'}\n";
# Send email (use proper email module in production)
open(my $mail, '|-', '/usr/sbin/sendmail', '-t') or die "Cannot send: $!";
print $mail "To: $recipient\n";
print $mail "Subject: $subject\n";
print $mail "Content-Type: text/plain; charset=UTF-8\n\n";
print $mail $body;
close($mail);
# Redirect or show success
if ($redirect) {
print $cgi->redirect($redirect);
} else {
print $cgi->header();
print "<h1>Thank You</h1><p>Your message has been sent.</p>";
}
<?php
// Modern PHP Form Handler
// Configuration - SECURITY
$allowed_recipients = ['[email protected]', '[email protected]'];
$allowed_domains = ['yourdomain.com', 'www.yourdomain.com'];
// CSRF Protection
session_start();
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die('Invalid CSRF token');
}
// Validate referer
$referer = parse_url($_SERVER['HTTP_REFERER'] ?? '', PHP_URL_HOST);
if (!in_array($referer, $allowed_domains)) {
die('Invalid referer');
}
// Get and sanitize form data
$recipient = filter_var($_POST['recipient'] ?? '', FILTER_SANITIZE_EMAIL);
$subject = htmlspecialchars($_POST['subject'] ?? 'Form Submission', ENT_QUOTES, 'UTF-8');
$redirect = filter_var($_POST['redirect'] ?? '', FILTER_SANITIZE_URL);
// Validate recipient
if (!in_array(strtolower($recipient), array_map('strtolower', $allowed_recipients))) {
die('Invalid recipient');
}
if (!filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
die('Invalid email format');
}
// Build message
$message = "Form submission received:\n\n";
$excluded = ['recipient', 'subject', 'redirect', 'csrf_token'];
foreach ($_POST as $key => $value) {
if (in_array($key, $excluded)) continue;
$clean_key = htmlspecialchars($key, ENT_QUOTES, 'UTF-8');
$clean_value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
$message .= "$clean_key: $clean_value\n";
}
$message .= "\n--- Environment ---\n";
$message .= "IP: " . $_SERVER['REMOTE_ADDR'] . "\n";
$message .= "User Agent: " . $_SERVER['HTTP_USER_AGENT'] . "\n";
// Send email
$headers = [
'From: [email protected]',
'Reply-To: ' . ($_POST['email'] ?? '[email protected]'),
'X-Mailer: PHP/' . phpversion(),
'Content-Type: text/plain; charset=UTF-8'
];
if (mail($recipient, $subject, $message, implode("\r\n", $headers))) {
if ($redirect) {
header("Location: $redirect");
exit;
}
echo "<h1>Thank You</h1><p>Your message has been sent.</p>";
} else {
echo "<h1>Error</h1><p>Failed to send message.</p>";
}
// Modern Node.js/Express Form Handler
const express = require('express');
const nodemailer = require('nodemailer');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const app = express();
app.use(helmet());
app.use(express.urlencoded({ extended: true }));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10 // limit each IP to 10 requests per window
});
// Configuration
const ALLOWED_RECIPIENTS = ['[email protected]'];
const ALLOWED_DOMAINS = ['yourdomain.com'];
// Email transporter
const transporter = nodemailer.createTransport({
host: 'smtp.yourdomain.com',
port: 587,
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
});
app.post('/contact', limiter, async (req, res) => {
try {
const { recipient, subject, redirect, ...formData } = req.body;
// Validate referer
const referer = new URL(req.headers.referer || '').hostname;
if (!ALLOWED_DOMAINS.includes(referer)) {
return res.status(403).send('Invalid referer');
}
// Validate recipient
if (!ALLOWED_RECIPIENTS.includes(recipient?.toLowerCase())) {
return res.status(400).send('Invalid recipient');
}
// Build message
let message = 'Form submission received:\n\n';
for (const [key, value] of Object.entries(formData)) {
message += `${key}: ${value}\n`;
}
message += `\n--- Environment ---\n`;
message += `IP: ${req.ip}\n`;
message += `User Agent: ${req.headers['user-agent']}\n`;
// Send email
await transporter.sendMail({
from: '[email protected]',
to: recipient,
subject: subject || 'Form Submission',
text: message
});
if (redirect) {
res.redirect(redirect);
} else {
res.send('<h1>Thank You</h1><p>Message sent.</p>');
}
} catch (error) {
console.error(error);
res.status(500).send('Error sending message');
}
});
app.listen(3000);
<!-- Contact Form using FormMail -->
<form action="/cgi-bin/FormMail.pl" method="POST">
<!-- Configuration fields -->
<input type="hidden" name="recipient" value="[email protected]">
<input type="hidden" name="subject" value="Website Contact Form">
<input type="hidden" name="redirect" value="https://yourdomain.com/thank-you.html">
<input type="hidden" name="required" value="name,email,message">
<input type="hidden" name="sort" value="order:name,email,phone,message">
<div class="mb-3">
<label for="name" class="form-label">Name *</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email *</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="phone" class="form-label">Phone</label>
<input type="tel" class="form-control" id="phone" name="phone">
</div>
<div class="mb-3">
<label for="message" class="form-label">Message *</label>
<textarea class="form-control" id="message" name="message" rows="5" required></textarea>
</div>
<button type="submit" class="btn btn-primary">Send Message</button>
</form>
Download the FormMail script package:
Common causes and solutions:
$mailprog points to your actual sendmail location@recipients arrayYes! Separate email addresses with commas in the recipient field:
<input type="hidden" name="recipient" value="[email protected],[email protected]">
Important: All recipients must be listed in the @recipients whitelist in the script.
Check these common issues:
http:// or https://<!-- Correct -->
<input type="hidden" name="redirect" value="https://yourdomain.com/thank-you.html">
<!-- Incorrect -->
<input type="hidden" name="redirect" value="thank-you.html">
FormMail has been heavily exploited by spammers. Essential security measures:
The original unmodified FormMail has serious security vulnerabilities:
If you must use FormMail, use a patched version with proper security configurations. Better alternatives include modern form services or server-side frameworks with built-in security.
Modern form handling solutions include:
Two options:
redirect field to send users to your own thank-you pagetitle, return_link_url, and return_link_title fields<input type="hidden" name="title" value="Thank You!">
<input type="hidden" name="return_link_url" value="https://yourdomain.com">
<input type="hidden" name="return_link_title" value="Back to Homepage">
Yes, with modifications. Several Windows ports exist:
See the "Ports" section in the original documentation for specific Windows versions.
See examples of FormMail implementations:
Historical add-ons and modifications derived from FormMail:
Additions including courtesy reply email, flatfile database logging, and fax-to-email support.
Optional courtesy reply and database logging of all form submissions.
Personalized and automated email responses to form submissions.
Minimalized FormMail allowing you to email files to users.
FormMail spin-off for emailing files to users.
Visitor comment system - if you need public feedback display.
Manage email subscriptions and send newsletters.