Writing a Simple Form Mailer

This tutorial will show you how to build a simple form mailer script, so that visitors can send you emails from your site. Along the way we explain some new Perl concepts, including the CGI library module.

In this tutorial we'll show you how to create a simple form mailer CGI script in Perl. A form mailer script allows you to add feedback forms to your site. Visitors fill in the forms, and the information they fill in is emailed to you.

CGI form handling can be quite an involved process. Luckily the Perl CGI module, CGI.pm, can do most of the tricky stuff for you! We'll use CGI.pm in this tutorial to build a nice simple form mailer.

Creating the form

The first thing to do is to build the HTML form that your visitors can fill in. We're going to create a very simple form that just has fields for the user's email address and their comments; however, once you've mastered the technique, you can easily add as many fields as you need to the form.

If you're not familiar with creating HTML forms, you may like to read our HTML Forms tutorial first.

Here is the HTML for our simple form:


<html>
<head></head>
<body>

<form action="/cgi-bin/form_mailer.cgi" method="post">

<p>Your Email Address:<br>
<input type="text" name="email_address" size="40"></p>
<p>Your Comments:<br>
<textarea name="comments" cols="50" rows="10"></textarea>

<p><input type="submit" name="send" value="Send Email"></p>

</form>

</body>
</html>

Note that our form will send two parameters to our script: one called email_address and one called comments.

Call this page something like mailform.html, and upload it to your site somewhere.

Now let's build the CGI script itself!

Sponsor message

Unlimited Autoresponders & Newsletters. 99.34% email delivered. Free support. Click Here for more info!

Using CGI.pm

CGI.pm is a Perl library module. It does a lot of the CGI "nitty-gritty" for you, which makes writing CGI scripts in Perl a lot easier!

Most Web servers these days tend to come with the CGI.pm module already installed. However, if you need to install it yourself, you can grab it here.

To use the CGI module in your scripts, you first tell Perl that you want to use it. This is done as follows:


use CGI;

Easy! :)

The next step is to create a CGI object. The CGI library is object-oriented, which means that all the functions and data to do with CGI are accessed through an object.

You can create the CGI object like this:


my $query = new CGI;

This creates a new CGI object and assigns a variable, $query, to point to it.

Note that this Perl variable name has a dollar sign ($) before it. This tells Perl that it is a scalar variable. Scalar variables can be used to store things like object pointers, numbers, and strings of text. (For more info see this tutorial.)

Note also that we are declaring the variable to be a private variable using the my operator. This ensures that the variable will only exist within this CGI script. This is a good practice to get into, and will help you to avoid problems when developing more complex Perl scripts in the future!

Printing out the HTTP header

You may remember from the Your first CGI script tutorial that CGI scripts need to output a Content-type HTTP header before anything else. It would be a good idea to print this out from our script now.

Luckily, the CGI object provides a handy way to output the Content-type header:


print $query->header ( );

(You can specify the content type between the ( ) if required, e.g. $query->header(-type=>'text/plain'); however, if you leave the brackets empty then it defaults to a content type of text/html, which is usually what you want.)

Getting the form information

Now we have our CGI object, $query, we can use it to retrieve the information that the user filled in. The information is passed to CGI scripts as parameters. To access a parameter using the CGI object, we use the following syntax:


$variable_name = $query->param("parameter_name");

So, if we want to retrieve the values of our two form parameters and store them in Perl variables, we can use something like this:


my $email_address = $query->param("email_address");
my $comments = $query->param("comments");

This code takes the values from our two form parameters, email_address and comments, and stores them in two Perl variables, $email_address and $comments.

Filtering the input

Whenever accepting data from an "untrusted" source, such as the our form, we should filter the data to ensure it doesn't contain anything malicious. For example, without filtering the data in the $email_address and $comments variables, it would be quite easy for a hacker to use our form mailer script to send out spam to thousands of people!

When writing real-world Web applications and scripts, always filter any data that the script receives from an untrusted source. This includes any data from form posts, GET variables in query strings, uploaded files and anything else that might have been created by someone, or something, you don't control.

To filter our data, let's write a couple of simple filter functions:


sub filter_field
{
  my $field = shift;
  $field =~ s/From://gi;
  $field =~ s/To://gi;
  $field =~ s/BCC://gi;
  $field =~ s/CC://gi;
  $field =~ s/Subject://gi;
  $field =~ s/Content-Type://gi;
  return $field;
}

sub filter_header_field
{
  my $field = shift;
  $field =~ s/From://gi;
  $field =~ s/To://gi;
  $field =~ s/BCC://gi;
  $field =~ s/CC://gi;
  $field =~ s/Subject://gi;
  $field =~ s/Content-Type://gi;
  $field =~ s/[\0\n\r\|\!\/\<\>\^\$\%\*\&]+/ /g;
  return $field;
}

These functions strip out anything in the data that could be used to trick our script into sending spam. filter_field is good for general data (such as our $comments variable), while filter_header_field is a bit more thorough and is suited to header data, such as the "from:" and "to:" email addresses (which are the most vulnerable to hacking).

We'll place these functions at the end of our script, and call them as follows:


$email_address = filter_header_field ( $email_address );
$comments = filter_field ( $comments );

Our input values have now been filtered against hacking attempts.

Emailing the results

Now that we have the values of our two form fields, and we know that those values won't contain any malicious text, it's time to email them to the webmaster (i.e. you!).

Most UNIX and Linux Web servers include a handy program called sendmail that you can use to send emails from your CGI scripts. (If you have a Windows server or hosting account, you'll need to ask your Web hosting company how to send emails from your CGI scripts, as the technique varies from server to server.)

To send the email, we open a pipe to the sendmail program, then output our email (including header information such as the sender, recipient and email subject) to the pipe:


open ( MAIL, "| /usr/lib/sendmail -t" );
print MAIL "From: $email_address\n";
print MAIL "To: webmaster\@yourdomain.com\n";
print MAIL "Subject: Form Submission\n\n";
print MAIL "$comments\n";
print MAIL "\n.\n";
close ( MAIL );

You'll need to change 2 things in this block of code:

  1. Change /usr/lib/sendmail to your actual path to sendmail. (Often /usr/lib/sendmail will work fine, but if it doesn't, check with your Web hosting company to find the correct path for your server.)

  2. Change webmaster\@yourdomain.com to your actual email address (the address you'd like the form results to be sent to). Note that the \ before the @ symbol is required - more about this in a later tutorial!

By the way, the line:


print MAIL "\n.\n";

prints a dot (.) on a line on its own at the end of the email. This tells sendmail that we've finished outputting the email message. sendmail will then send the email.

Thanking the visitor

Finally, let's display a short page to the visitor, thanking them for filling in the form:


print <<END_HTML;
<html>
<head></head>
<body>Thanks for filling in our form!</body>
</html>
END_HTML

The finished script

That's all there is to it! Let's put it all together so you can see the whole script. We've added comments in the code (the lines beginning with #) to help you work out which bit does what:


#!/usr/bin/perl

use CGI;

# Create the CGI object
my $query = new CGI;

# Output the HTTP header
print $query->header ( );

# Capture the form results
my $email_address = $query->param("email_address");
my $comments = $query->param("comments");

# Filter the form results
$email_address = filter_header_field ( $email_address );
$comments = filter_field ( $comments );

# Email the form results
open ( MAIL, "| /usr/lib/sendmail -t" );
print MAIL "From: $email_address\n";
print MAIL "To: webmaster\@yourdomain.com\n";
print MAIL "Subject: Form Submission\n\n";
print MAIL "$comments\n";
print MAIL "\n.\n";
close ( MAIL );

# Thank the user
print <<END_HTML;
<html>
<head></head>
<body>Thanks for filling in our form!</body>
</html>
END_HTML

# Functions for filtering user input

sub filter_field
{
  my $field = shift;
  $field =~ s/From://gi;
  $field =~ s/To://gi;
  $field =~ s/BCC://gi;
  $field =~ s/CC://gi;
  $field =~ s/Subject://gi;
  $field =~ s/Content-Type://gi;
  return $field;
}

sub filter_header_field
{
  my $field = shift;
  $field =~ s/From://gi;
  $field =~ s/To://gi;
  $field =~ s/BCC://gi;
  $field =~ s/CC://gi;
  $field =~ s/Subject://gi;
  $field =~ s/Content-Type://gi;
  $field =~ s/[\0\n\r\|\!\/\<\>\^\$\%\*\&]+/ /g;
  return $field;
}

Call this file form_mailer.cgi and upload it to your CGI scripts folder (usually cgi-bin) on your server. Don't forget to upload in ASCII mode, and to set execute permission on the file after you've uploaded it! (For more info on these points, read the Your first CGI script tutorial.)

Check your HTML form to make sure that the URL of your CGI script in the <form> tag is correct.

Finally you're ready to test your form mailer! Enter the URL of your form in your browser's address bar, then fill in your email address and some comments, and press the Send Email button. If all goes well, you should shortly receive your comments in an email!

Troubleshooting the script

If you get the dreaded Internal Server Error message, don't panic! It's probably a simple problem. Make sure your paths to Perl and sendmail are correct, and that you uploaded your file in ASCII format and set the correct permissions. If you still have problems, check that your script doesn't have any typos in, and that it looks like the version in our "The finished script" section above.

It's also a good idea to work through our Troubleshooting CGI scripts tutorial.

If your script still doesn't work, there may be some configuration problem with your server - ask your web hosting company's technical support staff for help.

Follow Elated

Related articles

Responses to this article

20 most recent responses (oldest first):

09-Oct-09 00:12
Hi tjstab, and welcome!

Maybe your 'comments' form field name is different to the parameter name in your script?

If you can post your script and HTML form here then I can take a look for you.

Cheers,
Matt
20-Dec-10 17:40
Finally, I set up a contact form that works! Thank you for your article!

Instead of the "end html" message, can I have the submitter directed to a different html document? Is there a way to insert a link instead of a message?

Thank you
03-Jan-11 03:09
@Will: Sorry for the delay in replying (Xmas and all that!).

Yes, you could include a link in the output HTML, eg:


<body>Thanks for filling in our form! <a href="page.html">Click here to continue</a></body>


Or you could issue a redirect to take the user to the new page automatically:

http://search.cpan.org/dist/CGI.pm/lib/CGI.pm#GENERATING_A_REDIRECTION_HEADER

Hope that helps
Matt
03-Jan-11 14:25
Thanks! I think I'll try that redirect issue.

I appreciate you help.

Sincerely,
Will
06-Jan-11 14:17
Matt,

I tried adding a second contact form, and made a second cgi file, but named it differently. And then of course I specified the second file in my form action=" " . But it doesn't work.

Do I have to name the cgi file, "form_mailer.cgi" in order for this to work?

Thanks,
Will
06-Jan-11 23:46
@Will: Sounds like you did the right things. What error are you getting?
07-Jan-11 11:09
I get Internal Server Error - "The server encountered an internal error or misconfiguration and was unable to complete your request."

Here's the cgi file:




#!/usr/bin/perl

use CGI;

my $query = new CGI;

print $query->header ( );

my $name = $query->param("name");
my $phone_number = $query->param("phone_number");
my $email_address = $query->param("email_address");
my $message = $query->param("message");
my $referral = $query->param("referral");
my $email = $query->param("email");
my $phone = $query->param("phone");



$email_address = filter_header_field ( $email_address );
$comments = filter_field ( $comments );

open ( MAIL, "| /usr/sbin/sendmail -t" );
print MAIL "From: $email_address\n";
print MAIL "To: contact\@willjackson.com\n";
print MAIL "Subject: Album Design Form Submission\n\n";
print MAIL "$name\n";
print MAIL "$phone_number\n";
print MAIL "$email_address\n";
print MAIL "$message\n";
print MAIL "$referral\n";
print MAIL "$email\n";
print MAIL "$phone\n";
print MAIL "\n.\n";
close ( MAIL );

print <<END_HTML;

<html>
<head>
<link href="../style.css" media="screen" rel="stylesheet" type="text/css" />
</head>
<body>
<br /><br />
<h1>Thank you!</h1><br /><br />
<p>Someone should be contacting you shortly. If you don't hear from us within 48 hours, please call, 636-464-9475. Thank you.</p><br />
<br /><br />
<br /><br />

<div id="footer">
<p>Copyright
&copy; Will Jackson Photography, St. Louis MO. All&nbsp;rights&nbsp;reserved.</p>
</div>

</body>
</html>




END_HTML

sub filter_field
{
my $field = shift;
$field =~ s/From://gi;
$field =~ s/To://gi;
$field =~ s/BCC://gi;
$field =~ s/CC://gi;
$field =~ s/Subject://gi;
$field =~ s/Content-Type://gi;
return $field;
}

sub filter_header_field
{
my $field = shift;
$field =~ s/From://gi;
$field =~ s/To://gi;
$field =~ s/BCC://gi;
$field =~ s/CC://gi;
$field =~ s/Subject://gi;
$field =~ s/Content-Type://gi;
$field =~ s/[\0\n\r\|\!\/\<\>\^\$\%\*\&]+/ /g;
return $field;
}

07-Jan-11 15:13
Matt,

I have it figured out. I had forgot to set file permissions on the second cgi file I uploaded.

Thank you
09-Jan-11 19:29
@Will: Glad it's working now.
07-Jun-11 16:52
First of all I want to thank you for this tutorial

I have used it for numerous form, applications etc, and it works just fine until I needed to send a copy of the application back to the user

So I want to take the email address of the user from the form and use it for a CC in the script...I thought it would be a simple task but I was wrong again

I start with getting the form data:



my $recipientCC = $query->param("email_address");



Then I do a little check (and I also try to add the necessary \@ and \n at the end) :



if ($apptype eq "Staff")
{
$recipient = "jazztheblower\@gmail.com\n";
$recipientCC =~ s/(.*)(@.*)/$1\\$2\n/;
}



and finally I print it to the mail:



print MAIL "CC: $recipientCC";



So here's what happens:

I get the mail fine for my main recipient, but the CC should be webform2010@gmail.com but ends up as webform2010\@gmail.com@myservermailerdemon.com

I am slightly slipping into madness here I have tried changing a lot of things, like switching from a line feed \n to the actual characters, etc but nothing works

Please o'wise one, do you have any inputs on this problem?

Thanks in advance

[Edited by kingjazz on 07-Jun-11 17:15]
20-Jun-11 05:23
@kingjazz: Hmm, it could be an anti-spam mechanism inside your mail server. Is it escaping the '@' symbol like that? (\@)

Maybe try just sending 2 copies of the email, one to the main recipient and another to the CC recipient (using TO: instead of CC:)?

Also your regex might be messing things up (not sure). Try commenting it out and see if it works maybe?

Matt
08-Aug-11 03:34
Hi, thanks for that fantastic and very simple explanation.

I think I've done everything right, and I'm hoping what I'm about to ask is reasonably simple.

When I press 'send' I get this error:

"Not Found

The requested document was not found on this server.
Web Server at massageyou.co.nz"

the URL points to www.massageyou.co.nz/cgi-bin/formmailer.cgi as per the HTML. My CGI script is in the cgi-bin of my hosting account. However, my site is in the HTTPDocs folder of my hosting account. So I think the problem is one of two things:

1) My path is wrong. In which case, is it safe for me to move the cgi-bin folder into my HTTPDocs folder? Or is that likely to destroy things in the background that I can't fix? Or, do you know what the path might be? (Sorry, I know the path could be anything, but I'm just assuming that my hosting account is set up in a fairly standard way.

2) I'm doing something else wrong - in which case - any ideas?

Thanks.
Tom

[Edited by tomnorman on 08-Aug-11 03:35]
09-Aug-11 05:10
@tomnorman: Check with your hosting company to see what setup they recommend for running Perl CGI scripts. It may be that you need to set certain permissions, use a different file extension such as .pl, or, as you say, move your cgi-bin folder into your document root.
18-Nov-11 07:47
Hi,
Thanks for this tutorial.
I tried running this form mailer from my local system and i got this error.

The server encountered an internal error or misconfiguration and was unable to complete your request.

I don't know what to do.
20-Nov-11 17:56
I can feel it, I am insanely close! Unfortunately i am having the same issue as tjstab and am getting a blank email...

here's the form code:



<form action="/form-mailer.cgi" method="post" enctype="text/plain">
<input type="text" name="mail" value="Your Email" />
<input type="submit" value="Join">
</form>


and here's the cgi:



#!/usr/local/bin/perl

use CGI;

# Create the CGI object
my $query = new CGI;

# Output the HTTP header
print $query->header ( );

# Capture the form results
my $email_address = $query->param("email_address");

# Filter the form results
$email_address = filter_header_field ( $email_address );

# Email the form results
open ( MAIL, "| /usr/sbin/sendmail -t" );
print MAIL "From: $email_address\n";
print MAIL "To: subscribe\@disstory.com\n";
print MAIL "Subject: Form Submission\n\n";
print MAIL "$comments\n";
print MAIL "\n.\n";
close ( MAIL );

# Thank the user
print <<END_HTML;
<html>
<head></head>
<body>Thanks for filling in our form!</body>
</html>
END_HTML

# Functions for filtering user input

sub filter_field
{
my $field = shift;
$field =~ s/From://gi;
$field =~ s/To://gi;
$field =~ s/BCC://gi;
$field =~ s/CC://gi;
$field =~ s/Subject://gi;
$field =~ s/Content-Type://gi;
return $field;
}

sub filter_header_field
{
my $field = shift;
$field =~ s/From://gi;
$field =~ s/To://gi;
$field =~ s/BCC://gi;
$field =~ s/CC://gi;
$field =~ s/Subject://gi;
$field =~ s/Content-Type://gi;
$field =~ s/[\0\n\r\|\!\/\<\>\^\$\%\*\&]+/ /g;
return $field;
}


any help would be GREATLY appreciated!
Thank you!
21-Nov-11 23:59
@imokhaijoseph: Use CGI::Carp to view the exact error message:

http://perldoc.perl.org/CGI/Carp.html


use CGI::Carp qw(fatalsToBrowser);


@cordovad88: You seem to have missed out the lines of code relating to the comments field. This is why your message is blank.

eg:


my $comments = $query->param("comments");


is missing.
22-Nov-11 17:28
Thanks for the quick reply!

i added the lines to the code but.. no luck... any other ideas?

Thanks so much for your help.
23-Nov-11 21:19
@cordovad88: By "no luck" you mean you're still getting a blank email?

Your form doesn't seem to have a "comments" field in it either. You've also renamed the "email_address" field to "mail" in your form, which will break the script.
04-Feb-13 12:34
Hi, Can you explain how the filters work? (breakdown of the code) Thank you!
11-Feb-13 02:22
@brookln: filter_field should be self-explanatory. The last regular expression in filter_header_field filters out various unwanted characters that shouldn't be in email headers: NULLs (\0), line feeds (\n), carriage returns (\r) and so on.

View all 21 responses »

Post a response

Want to add a comment, or ask a question about this article? Post a response.

To post responses you need to be a member. Not a member yet? Signing up is free, easy and only takes a minute. Sign up now.

Top of Page