• Skip to main content
  • Skip to primary sidebar
  • Skip to footer

Matt Doyle | Elated Communications

Web and WordPress Development

  • About Me
  • Blog
    • Design & Multimedia
      • Photoshop
      • Paint Shop Pro
      • Video & Audio
    • Online Marketing
      • E-Commerce
      • Social Media
    • Running a Website
      • WordPress
      • Apache
      • UNIX and Linux
      • Using FTP
    • Web Development
      • HTML
      • CSS
      • JavaScript
      • PHP
      • Perl and CGI Scripting
  • Portfolio
  • Contact Me
  • Hire Me
Home / Blog / Web Development / Perl and CGI Scripting / Writing a Simple Form Mailer

Writing a Simple Form Mailer

11 March 2003 / 21 Comments

In this tutorial I’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!

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.

Filed Under: Perl and CGI Scripting Tagged With: cgi, cgi-bin, example, form-to-email script, formmail, perl, web programming

Reader Interactions

Comments

  1. tjstab says

    8 October 2009 at 6:32 pm

    The script works fine and I receive an e-mail but the problem is when I open it it’s blank (absolutely no text). Any suggestions??

    Reply
  2. matt says

    9 October 2009 at 12:12 am

    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

    Reply
  3. Will says

    20 December 2010 at 5:40 pm

    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

    Reply
  4. matt says

    3 January 2011 at 3:09 am

    @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

    Reply
  5. Will says

    3 January 2011 at 2:25 pm

    Thanks! I think I’ll try that redirect issue.

    I appreciate you help.

    Sincerely,
    Will

    Reply
  6. Will says

    6 January 2011 at 2:17 pm

    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

    Reply
  7. matt says

    6 January 2011 at 11:46 pm

    @Will: Sounds like you did the right things. What error are you getting?

    Reply
  8. Will says

    7 January 2011 at 11:09 am

    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;
    }
    
    
    Reply
  9. Will says

    7 January 2011 at 3:13 pm

    Matt,

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

    Thank you

    Reply
  10. matt says

    9 January 2011 at 7:29 pm

    @Will: Glad it’s working now. πŸ™‚

    Reply
  11. kingjazz says

    7 June 2011 at 4:52 pm

    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 :p

    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 :p 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.comn";
     $recipientCC =~ s/(.*)(@.*)/$1\$2n/;
    }
    

    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 :p

    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]

    Reply
  12. matt says

    20 June 2011 at 5:23 am

    @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

    Reply
  13. tomnorman says

    8 August 2011 at 3:34 am

    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 http://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]

    Reply
  14. matt says

    9 August 2011 at 5:10 am

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

    Reply
  15. imokhaijoseph says

    18 November 2011 at 7:47 am

    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.

    Reply
  16. cordovad88 says

    20 November 2011 at 5:56 pm

    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!

    Reply
  17. matt says

    21 November 2011 at 11:59 pm

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

    Reply
  18. cordovad88 says

    22 November 2011 at 5:28 pm

    Thanks for the quick reply!

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

    Thanks so much for your help.

    Reply
  19. matt says

    23 November 2011 at 9:19 pm

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

    Reply
  20. brookln says

    4 February 2013 at 12:34 pm

    Hi, Can you explain how the filters work? (breakdown of the code) Thank you!

    Reply
  21. matt says

    11 February 2013 at 2:22 am

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

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

To include a block of code in your comment, surround it with <pre> ... </pre> tags. You can include smaller code snippets inside some normal text by surrounding them with <code> ... </code> tags.

Allowed tags in comments: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre> .

Primary Sidebar

Hire Matt!

Matt Doyle headshot

Need a little help with your website? I have over 20 years of web development experience under my belt. Let’s chat!

Matt Doyle - Codeable Expert Certificate

Hire Me Today

Call Me: +61 2 8006 0622

Stay in Touch!

Subscribe to get a quick email whenever I add new articles, free goodies, or special offers. I won’t spam you.

Subscribe

Recent Posts

  • Make a Rotatable 3D Product Boxshot with Three.js
  • Speed Up Your WordPress Website: 11 Simple Steps to a Faster Site
  • Reboot!
  • Wordfence Tutorial: How to Keep Your WordPress Site Safe from Hackers
  • How to Make Awesome-Looking Images for Your Website

Footer

Contact Matt

  • Email Me
  • Call Me: +61 2 8006 0622

Follow Matt

  • E-mail
  • Facebook
  • GitHub
  • LinkedIn
  • Twitter

Copyright © 1996-2023 Elated Communications. All rights reserved.
Affiliate Disclaimer | Privacy Policy | Terms of Use | Service T&C | Credits