FormMail

A generic WWW form to email gateway that parses the results of any HTML form and sends them to specified email addresses.

Perl v1.6 Security Critical

Quick Info

  • Version: 1.6
  • Released: May 02, 1997
  • Language: Perl 5+
  • License: Free for use
  • Files: FormMail.pl, README

Historical Context

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."

NMS Project Documentation

Timeline of Issues

  • 1995: FormMail created as free CGI script
  • 1997: Becomes most downloaded CGI script
  • 2001: First major vulnerability disclosed (CVE-2001-0357)
  • 2002: Listed as #3 attack vector by SecurityFocus
  • 2002: London Perl Mongers create NMS replacements
  • 2009: Version 1.93 finally addresses security issues

Secure Modern Alternatives

These alternatives offer FormMail-compatible functionality with proper security:

Self-Hosted (Drop-in Replacements)

NMS FormMail

Drop-in replacement by London Perl Mongers. Uses strict, taint checking, CGI.pm. Recommended for production use.

Free Perl

Tectite FormMail

23-year track record with only 4 minor security flaws. PHP version available. Professional support.

Free PHP/Perl

PHPMailer

Full-featured email library for PHP. SMTP support, attachments, HTML email.

Free MIT

Third-Party Services (No Server Required)

Formspree

Form backend for static sites. Just point your form action to their URL.

Free 50/mo From $8/mo

Web3Forms

Free form API. No signup for basic use. Spam filtering included.

Free 250/mo No signup

Netlify Forms

Built into Netlify hosting. Just add netlify attribute to your form.

100 free/mo Netlify

Security Comparison

Feature Original FormMail NMS/Tectite Third-Party
Header injection protectionNoYesYes
CSRF protectionNoOptionalYes
Rate limitingNoOptionalYes
Spam filteringNoBasicAI-powered
CAPTCHA supportNoYesYes
Server requiredYesYesNo

Overview

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.

Key Benefits

  • No programming knowledge required for multiple forms
  • Single script handles unlimited forms
  • Perfect system-wide solution for user feedback
  • Configurable through hidden form fields
  • Multiple formatting options for email output

Package Contents

File Description
FormMail.pl The Perl script that processes form data and sends emails
README Installation instructions and configuration guide

Features

Form Processing

  • Parses any HTML form automatically
  • Supports all input types (text, textarea, select, checkbox, radio)
  • Required field validation
  • Custom success/error pages

Email Options

  • Multiple recipients support
  • Custom subject lines
  • Reply-to address configuration
  • Multiple output formats

Form Configuration Fields

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

Installation

Step 1: Download and Extract

Download the FormMail package and extract it.

Step 2: Configure the Script

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';

Step 3: Upload to Server

Upload FormMail.pl to your CGI-bin directory.

Step 4: Set Permissions

chmod 755 FormMail.pl

Step 5: Create Your Form

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>

Configuration Options

Script Variables

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'

Code Examples

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);

HTML Form Template

<!-- 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

Download the FormMail script package:

View Individual Files

Frequently Asked Questions

Common causes and solutions:

  1. Check spam folder - FormMail emails often get flagged as spam
  2. Verify sendmail path - Make sure $mailprog points to your actual sendmail location
  3. Check recipient whitelist - Ensure recipient is in @recipients array
  4. Server mail configuration - Your host may have restrictions on sendmail
  5. Check server logs - Look for errors in your CGI error log

Yes! 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:

  • Use a complete URL including http:// or https://
  • Ensure the redirect URL is accessible and valid
  • Check that no output is sent before the redirect header
  • Verify there are no PHP/Perl errors preventing execution
<!-- 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:

  • Recipient whitelist: Only allow specific email addresses
  • Referer checking: Restrict to your domain only
  • Rate limiting: Limit submissions per IP
  • CAPTCHA: Add reCAPTCHA or similar
  • Honeypot fields: Hidden fields that bots fill out
  • Modern alternatives: Consider using services like Formspree or Netlify Forms

The original unmodified FormMail has serious security vulnerabilities:

  • Email header injection attacks
  • Open relay for spam
  • No CSRF protection
  • Limited input validation

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:

  • Formspree - Simple form backend
  • Netlify Forms - Built into Netlify hosting
  • Google Forms - Free and easy
  • Typeform - Beautiful form builder
  • PHP mail() - Built-in PHP function with proper validation
  • SendGrid/Mailgun APIs - Transactional email services
  • WordPress Contact Form 7 - For WordPress sites

Two options:

  1. Redirect to custom page: Use the redirect field to send users to your own thank-you page
  2. Customize built-in page: Use title, 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:

  • Use with Blat for command-line email sending
  • Use with Windmail for SMTP
  • Requires Perl for Windows (ActivePerl or Strawberry Perl)

See the "Ports" section in the original documentation for specific Windows versions.

View All FAQs | FormMail FAQ Archive

FormMail in Action

See examples of FormMail implementations:

FormMail Extras

Historical add-ons and modifications derived from FormMail:

BFormMail

Additions including courtesy reply email, flatfile database logging, and fax-to-email support.

yForm.cgi

Optional courtesy reply and database logging of all form submissions.

XFormMail

Personalized and automated email responses to form submissions.

MailFile Script

Minimalized FormMail allowing you to email files to users.

SendCGI

FormMail spin-off for emailing files to users.

Related Scripts

Guestbook Perl

Visitor comment system - if you need public feedback display.

Mailing List Perl

Manage email subscriptions and send newsletters.