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 system, replacing the <form>...</form> content with a confirmation report, and returning the reconstructed page to the visitor’s web browser. Users never leave the main website, never see popups or new tabs, and never have to navigate backward nor follow return links after submission.

In the event that the user does not complete a required field, FormMail++ returns the original form, inserts a formatted error message, shades the invalid form fields, and re-populates the submitted data (via JavaScript).

FormMail++ also transmits the submitted data as a multipart mime email containing both plain text and elegant html with visually useful styling. FormMail++ includes thorough css identifiers for completely customizable success confirmations and error notifications.


FormMail++ documentation is still being merged
so the original documentation remains available.


FormMail++ is designed to be a drop-in replacement/upgrade to FormMail so if a system supports FormMail, it should also support FormMail++ (see technical notes section for more precise statement).

It is suggested, but not required, that a FormMail++ user configure the following template values before uploading:

  • $htmlbase: The address of the script relative to your form page. Editing is not usually necessary because in most cases FormMail++ will extract this value from the url. If links and images on the outputted results are not correct, enter a relative path such as “/folder/” or a full address such as “”.
  • $template: The file name of the form page. This can be the name of the file (formpage.html), a relative path (../formpage.html), or an absolute path (/home/someuser/public_html/formpage.html). A web url will not work. As of v1.10, if no template is defined, FormMail++ will search its own folder for any similarly-named html files (that is, it will look for a file with the same name, but with a recognizable html file name extension) and use that file as the template.

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:

  • Upload in ASCII transfer mode
  • Set file permissions to 755 (rwxr-xr-x) or 711 (rwx–x–x)


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="FormMail++" 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.


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


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:

  • users may customize the script, but users shall not sell the modified code.
  • users shall not redistribute nor mirror the script (except for customizations, which may be redistributed).
  • users shall not delete attributions of authorship, but users may add personal attributions relating to their installations and customizations.
  • users shall not use FormMail++ for direct monetary gain, but may use FormMail++ in conjunction with activities that result in monetary gain so long as such gain results from the regular and customary activities. (Example: an individual or club website that sells promotional merchandise, but is not itself a retailer, and uses the script to transmit merchandise orders).

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.


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;


Like the original FormMail, FormMail++ runs in the Perl interpreter which is a core Apache module and thus is supported out-of-the-box by most web servers. Unlike FormMail, though, FormMail++ incorporates five non-essential CPAN modules. These modules should also be present out-of-the-box (see technical notes section if not).  Microsoft IIS users can install a Perl interpreter, though IIS users will probably also need to change the path for the sendmail pipe.

Perl Module Dependencies

FormMail++ incorporates five CPAN modules: Email::Valid (to validate submitter email addresses), CGI::Carp (for meaningful error reporting/debugging), Time::HiRes (to report execution speed), POSIX (to format time strings), CGI::apacheSSI (to process SSI directives within output), and LWP::Simple (to retrieve documentation). FormMail++ can run without these modules if their respective lines are removed from the source code. 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++ 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.

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). This array also suppresses the common submit and cancel buttons.
This array catches and maps the legacy config fields for realname and email and re-maps to name and sender, respectively, which are then stored in both the %Data{} and %Mail{} associative arrays.
Unlike the original script, FormMail++ stores all fields in %Data{} for easier printing and seeding into html output. For security, however, only %Mail{} is used when sending email.
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.


FormMail++ is not written for dynamically-generated content from PHP or Python since their html content is compiled by the daemon at the time of the request. However, this is unlikely to be an issue since php and py developers will probably already be using a php or py form handler. The limitation can be remedied using LWP to load the php source page via loopback. The confirmation html should be stored as a massive string variable (which Perl is exceptionally good at handling) and inserted back into the LWP-retrieved html via an s/<form.*form>/$output/m substitution. Of course, moderately skilled developers could easily port FormMail++ for use with php or python or simply integrate the architecture FormMail++ into an existing php or py form processor.

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.

Future Releases

Planned features and improvements include: file upload (sent as email attachment), grep to tolerate less well-formed form html, and script-emitted form html (for use as iframe or as server-side include).


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 organic documentation with LWP::Simple routine to retrieve documentation via internet.
1.17 2018/10/27 SSI parsing added via CGI::apacheSSI (note: CGI::apacheSSI exits prematurely on nested if stmts).
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 itself is also appended to this list.
1.19 2019/01/01 improved borrowed validation RegEx in &parse_stdin.
1.20 2020/07/23 fixed bug in &return_html that could misidentify the start of the contact form when extremely short filenames are used.
1.21 2020/10/05 revised deprecated naked qw(...) loop (i.e. foreach $i qw(...) => foreach $i (qw/.../). Added additional (albeit low-probability) method to use CGI:apacheSSI.
1.21 2020/__/__ change priority from simply escalating high priority to reflecting high, regular, or low.