FormMail++ John M. Woodruff, Ph.D. Inspired by Matt Wright's FormMail

version 1.19:   View  |  Save


FormMail++ is a next-generation html form handler inspired by the cgi script first developed by Matt Wright. When Matt first introduced FormMail in 1995, web hosting was expensive and cgi was inaccessible to most users. FormMail's novelty was converting form data into email. Today hosting is cheap but HTML has evolved far beyond the basic markup of the 1990s.

The novelty of FormMail++ is that its output is visually identical to the submitting form. It does this by reading the form page directly from the server's file space, replacing the <form>...</form> content with a confirmation report, and returning the reconstructed page to the visitor's web browser. When confronted with submission errors, FormMail++ reloads the original form, prints the appropriate error message(s), shades the invalid fields, and re-populates the form data (via JavaScript). But in every case, the user sees a page that looks identical to the original.

FormMail++ also constructs multi-part mime email messages containing both plain text and html parts for more elegant transmittal of the form data. Thorough css identifiers allow for completely customizable formatting of the confirmation data and/or error messages.

While FormMail++ is not written for dynamically-generated content from PHP or Python (which already offer the capacity for design congruity), developers are invited to adapt FormMail++'s code for other applications.


It is recommended that users upload FormMail++ to the same folder as the form page as default Apache configurations will allow execution of perl scripts from any folder. (If the Apache installation does not permit all-directory execution, move the script to the cgi-bin folder.) It is also recommended that both files be given the same name and different extensions (i.e. contact.html, When uploading to your server, note the following:

FormMail++ runs in the Perl 5 interpreter. Perl is a core Apache module and thus is supported by the overwhelming majority of web servers. FormMail itself utilizes five CPAN modules which should also be available by default. See the technical notes sections if the modules are not present, or have been inexplicably disabled by an administrator. Microsoft IIS users can install a Perl interpreter, though IIS users will probably also need to change the path for the "sendmail" pipe. In the course of modifying the script, a user might also consider the following:

Click to expand/collapse
HTML web Form

The submission form requires only a few basic fields. For those who are new to forms, or for those who are looking to get up and running quickly, the following html can be copy/pasted verbatim (except for the recipient email, of course).

 <form name="fmpp" action="" method="post">
 <table border="0" align="left">
   <th height="30" align="left">Name:</th>
   <td height="30" align="left"><input type="text" size="60" name="name" value=""></td>
   <th height="30" align="left">Email:</th>
   <td height="30" align="left"><input type="text" size="60" name="sender" value=""></td>
   <th height="30" align="left">Subject:</th>
   <td height="30" align="left"><input type="text" size="60" name="subject" value=""></td>
   <th height="30" align="left">Message:</th>
   <td height="30" align="left"><textarea cols="45" rows="10" name="message"></textarea></td>
   <td height="30" align="left" colspan="2">
     <input type="hidden" name="recipient" value="XXXXXXXXXXXXXXXXXXXXXXXXXXX">
     <input type="hidden" name="required"  value="name,sender,subject,message">
     <input type="hidden" name="sort_html" value="name,sender,subject,message">
     <input type="submit">
     <input type="reset">

Users familiar with Matt Wright's script may continue to use the same field names. Most legacy fields are ignored because FormMail++ precludes the need to control links and the like, but any critical legacy fields will be re-mapped internally. This table details the behavior-oriented form fields. Remember that behavior-oriented fields are special so they cannot be used for other purposes or as names of other fields.

Typical Form Fields
recipient: required Usually as a hidden input field, "recipient" may be specified either as a full email address, or as the user portion of the email address If a valid domain name is included, FormMail++ assumes it refers to an account on the local server. Specifying the recipient as "user" without the remainder of the email will almost certainly foil spiders and robots.
sender: required The email of the person submitting the form. This field will appear as the sender of the email. The legacy "email" maps here as well.
name: required The name of the person submitting the form. This field will appear as the sender of the email. The legacy "realname" maps here.
subject: required This field should be self-explanatory.
priority: optional As a checkbox or a selector, the specified value will cause FormMail++ to insert the relevant mail headers to mark the message as high, normal, or low. This, in turn, could be paired with mail filters to forward the emailed data to a cell phone or to another email account.
cc: optional As a checkbox, this field could be used for visitors to receive a copy of their own data, in which case the html creator would also want to use JavaScript to set cc.value to that of the sender.value and also to control the cc.checked property. Note that the checked value controls whether the associated field value is submitted by the form.
Invisible Fields
required: recommended List the fields that will require the user to complete before the form submission will be accepted. However, a valid sender and a valid recipient will always be required, regardless of its enumeration in this field.
sort_html: optional Control the order that form fields will be printed. If no fields are specified, all fields are printed in the order of appearance in the web form.
sort_mail: optional Control the presentation order of fields in the email message above the horizontal rule (i.e. to receive a concise overview of the form data). If no sort_mail is specified, the message field will be printed by itself followed by a horizontal rule. All form fields are then printed in the order of appearance in the web form. To suppress a particular field(s) from being printed, list that field with a prepended exclamation point (i.e. "!CookieMonster" would suppress the CookieMonster field from appearing anywhere in the email body).
pseudofrom: optional This allows specification of a mailbox with a domain that matches the hostname such as The advantage is that it will pass through spam filters more easily.
repliesto: optional In conjunction with pseudofrom, this field allows specification of an additional email address to receive replies. Starting with version 1.18b, formmail automatically inserts a reply-to header with both the recipient's email and the sender's email so that either person receiving it (the form recipient or the form sender via CC) can easily reach out to the other.
bcc: optional Identical to recipient except the email address(es) will not be disclosed to any joint bcc recipients or to a visitor who selected cc.

Click to expand/collapse
Results Page

The html generated by FormMail++ is accessible via css. The id and class references below allow the user to fully customize the appearance of the returned html. These identifiers can be used to suppress output via a "display:none" property (i.e. hiding the separator and/or the blank field rows). Similarly, if all that is desired is a success message, simply hide the entire table. Likewise, to use a disposition message, use css to hide the default disposition. Both <link> and <style> declarations in the form page's <head> section will work, but a linked style sheet might require a htmlbase declaration if it is in a different folder.

The first line containing the success or failure message.
The container for listing invalid form fields.
ol#error li.error
The enumerated invalid fields.
The table displaying the successfully-submitted data.
table#cgi tr.record
The row containing printed fields and values.
table#cgi tr.record td.field
The cell containing the field name (first/left column).
table#cgi tr.record td.value
The cell containing the field's value (second/right column).
table#cgi tr.separator
The row separating submitted fields from blank fields.
table#cgi tr.blanks
The row containing fields with empty values.
table#cgi tr.blanks td.field
The cell containing the field name (first/left column).
table#cgi tr.blanks td.value
An empty cell (second/right column).

For run-time efficiency, the confirmation data is outputted as a massive string, but users can also choose to line-wrap the returned html by setting 10000 to a lower value of choice (but this will prolong processing time).

Click to expand/collapse
Technical Notes

FormMail++ will read somewhat differently than Matt Wright's original script. Perl 5 no longer requires associative array hash references to contain quotes within the braces. Global variables have been minimized and are designated as global by a single uppercase letter in the name. Constants are also introduced using the all-uppercase convention. Lastly, local() has been replaced by the more efficient my(). Other stylistic changes include complete variable declaration at the start of each block and alignment of braces around blocks, which is simply a personal preference for visual identification.

Associative Arrays
FormMail++ associative arrays are significantly different. Incoming data is first screened for legacy "config" fields and discarded if found. Incoming data is then screened for legacy mail fields and, if encountered, re-mapped as appropriate. All data is then placed into a single array, during which time key mail fields are also copied into an array for mail headers so that they can be properly sanitized against malicious intent.

void: This array defines keys for legacy Config fields for FormMail (redirect, env_report, sort, print_config, print_blank_fields, title, return_link_url, return_link_title, missing_fields_redirect, background, bgcolor, text_color, link_color, vlink_color, alink_color). The %void{} array also suppresses the common "submit" and "cancel" buttons.
trap: This array catches and maps the legacy Config fields for "realname" and "email" which are mapped to "name" and "sender," respectively, and stored in the Data and Mail associative arrays.
data: Unlike the original script, FormMail++ stores all fields in %Data{} for easier printing and seeding into html output.
mail: During %Data{} population, name, sender, recipient, and subject are copied to %Mail{} and all %Mail{} are subsequently scrubbed for malicious characters. FormMail++ adds optional %Mail{} fields for cc and priority.

CPAN Modules
FormMail++ employs five CPAN modules: CGI::Carp, Time::HiRes, POSIX, Email::Valid, LWP::Simple, and CGI::apacheSSI. If needed, these modules are easily imported to the ubiquitous cPanel. Alternatively, they may be uploaded to a user's file space and their location specified with the use lib '/...' statement). These modules, in turn, use or require the following: CGI, vars, Fcntl, strict, warnings, IO::File, File::Spec, Mail::Address, Scalar::Util, LWP::UserAgent, HTTP::Status, HTTP::Date, Carp, Exporter, DynaLoader, XSLoader, APR::Pool, ModPerl::Util, Apache2::Response, Apache2::RequestRec, Apache2::RequestIO, Apache2::RequestUtil, IO::CaptureOutput, Net::DNS, Net::Domain::TLD, and Tie::Hash. This is provided as information only and no action should be required with respect to these modules.

FormMail++ cannot elegantly return a php source page since php is compiled by the daemon at the time of the request. However, this is unlikely to be an issue since php developers will probably be using a php form handler. But, the limitation can be remedied using LWP to load the php source page via loopback. The confirmation html should be outputted as a massive string, assigned to a variable, and inserted back into the LWP-retrieved html via a s/<form.*form>/$output/m substitution.

FormMail++ also expects the entire <form action...> tag to appear on a single line. There can only be one <form action...> directed at the script. If the page designer really wants two such forms on the page (weird?), the action attribute of each form must point to a unique script name. This means that there must be two FormMail++ scripts with different names. Otherwise, only the first <form action...> will be substituted.

Click to expand/collapse
Permissible Usage

FormMail++ is released by John Woodruff Semantics (a sole proprietorship). Any private person, sole proprietorship, or non-profit entity may use FormMail++ without cost as long as the following conditions are satisfied:

To obtain permission to use FormMail++ in a different manner, please contact the developer. This also applies to individual web developers wanting a contact form handler for their client sites. Permission may be free or a very nominal amount depending on the intended use.

Click to expand/collapse
FormMail++ Plans

The following features are planned for future releases:

Click to expand/collapse
FormMail++ Credits

The following lines are borrowed from Matt Wright's script:

$f =~ tr/+/ /;
$f =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$f =~ tr/\0//d;

$Data{required} =~ s/(\s+|\n)?,(\s+|\n)?/,/g;
$Data{required} =~ s/(\s+)?\n+(\s+)?//g;

$value =~ s/\&/\&amp;/gm;
$value =~ s/</\&lt;/gm;
$value =~ s/>/\&gt;/gm;
$value =~ s/"/\&quot;/gm;

Click to expand/collapse
FormMail++ History
ver released notes
1.00 2014/05/01 Hello World!
1.05 2014/05/15 realigned &about to more efficiently process errors; changed $about to use LWP::Simple for the foreseeable future; changed variable syntax to distinguish constants, globals, and locals; added use MIME::Base64 for future attachment capability; added use POSIX qw(strftime) and assigned a constant.
1.10 2014/06/01 added emit_ssi_form routine; added template lookup if no template is defined; cleaned up other variable references.
1.12 2014/06/15 reflowed configuration checks; reflowed directory reading; moved $MAILPROG statement to send_mail subroutine.
1.15 2014/09/08 added &return_header to include appropriate header status; shortened parsing within &return_html
1.16 2015/09/12 code tweaks; removed ssi output generation; replaced documentation with LWP::Simple
1.17 2018/10/27 SSI parsing added via CGI::apacheSSI (but seems to fail on nested if blocks
1.18a 2018/10/28 added pseudofrom and repliesto options
1.18b 2018/10/29 seeded recipient and sender to both appear in the reply-to header. Any reply-to supplied in the form page tiself is also appended to this list.
1.19 2019/01/01 improved borrowed validation RegEx in parse_stdin
1.20 2019/01/__ changed priority from simply escalating high priority to reflecting high, regular, or low.
Click to expand/collapse