Creating & Sending an HTML Email with a Dynamic PDF Attachment

This tutorial will explain how to get the data from a submitted form, create a PDF from that data, and then attach that PDF to an HTML email and finally send it. We will be using dompdf for the PDF generation, and Swift Mailer for sending the email.

Demo

Download Files

Step 1 - Download Libraries

Before we begin, we need to download the necessary files to create the PDF and email.

PDF Creation

dompdf

After doing a bit of research on a PHP class that can create dynamic PDF's, I found that dompdf was the best, most flexible, and easiest to use solution. It takes in straight HTML like you would find on any webpage, and converts it to a PDF. It can handle pretty much all CSS 2.1 selectors and attributes, and the same goes for its HTML capabilities.
You can download the package here.

HTML Mime Email Creation

Swift Mailer

Swift Mailer is the best PHP email library available right now. It's incredibly easy to use, and makes sending HTML emails with attachments and what not a simple task. You can get the package here.

Step 2 - File Structure

On top of our two libraries, we'll be creating 3 simple PHP files.

  • form.php - PHP page with form, will also process form
  • pdf.php - The layout for our PDF
  • html.php - Our HTML email template

File Structure

Step 3 - HTML Form

The Form

The form is where we'll be getting the "dynamic data" from for the PDF creation.
For this tutorial, it's just an average form with a few inputs and a submit button. There's also some simple CSS applied to make it look a little better on the eyes.
Normally, you would have some validation and all, but it's not necessary for this tutorial.
You can view the full source and CSS in the preview.

form.php

<form method="post" action="">
<label for="name">Your Name:</label>
<input type="text" name="name" id="name" class="input" />

<label for="email">Your Email:</label>
<input type="text" name="email" id="email" class="input" />

<label for="language">Favorite Language:</label>
<select name="language" id="language">
<option value="PHP">PHP</option>
<option value="HTML">HTML</option>
<option value="Javascript">Javascript</option>
<option value="CSS">CSS</option>
<option value="Other">Other</option>
</select>

<label for="about">Tell me about yourself:</label>
<textarea name="about" id="about" rows="4" cols="40"></textarea>

<p><button type="submit">Submit!</button></p>
</form>

We'll simply leave the action blank to submit right back to the same page

Step 4 - Process Form

Now that we have the form, lets get ready to do something when its submitted. We'll start with the code below:

<?php
if ( ! empty($_POST)) {

// Used for later to determine result
$success = $error = false;

// Object syntax looks better and is easier to use than arrays to me
$post = new stdClass;

// Usually there would be much more validation and filtering, but this
// will work for now.
foreach ($_POST as $key => $val)
$post->$key = trim(strip_tags($_POST[$key]));

// Check for blank fields
if (empty($post->name) OR empty($post->email) OR empty($post->about))
$error = true;

else {

// Create PDF and send email

}

}
?>

So what we're doing is creating a new object ($post) just b/c I prefer the syntax of objects rather than array's. They look a lot simpler and are easier to use to me also.
Then we loop through the $_POST array and do some very basic filtering. Normally you'd probably want to be a little safer, but this will do for now.
And again, if any fields are blank, show an error message. This would be replaced by possible Javascript validation or whatever.

Step 4 - Creating our PDF Layout

Now we'll need to figure out how we want our generated PDF to look. For this tutorial, it's going to be "stupid simple". Basically just a table echoing what the user entered in the form.

pdf.php

<html>
<head>
<style>
body {font-family:Helvetica, Arial, sans-serif; font-size:10pt;}
table {width:100%; border-collapse:collapse; border:1px solid #CCC;}
td {padding:5px; border:1px solid #CCC; border-width:1px 0;}
</style>
</head>
<body>

<h1>Form Results</h1>

<table>
<tr>
<td>Name:</td>
<td><?php echo $post->name; ?></td>
<td>Email:</td>
<td><?php echo $post->email; ?></td>
</tr>
<tr>
<td>Favorite Language:</td>
<td colspan="3"><?php echo $post->language; ?></td>
</tr>
<tr>
<td>About Yourself:</td>
<td colspan="3"><?php echo $post->about; ?></td>
</tr>
</table>

<p>You can make anything you want b/c dompdf accepts almost all 2.1 CSS attributes and selectors and almost all HTML elements.</p>

</body>
</html>

Step 5 - Creating the Actual PDF

Since we're going to be attaching the PDF to an email, we can't just output it straight to the browser. We need to capture its contents into a variable for later use.
To do this, we'll be using output buffering. Why? Since we need these PHP files to actually be executed (ie: echoing variables from the form data), we can't use something like file_get_contents() because it would just read the file as is without executing any PHP.

The following code will go inside that else statement we created above in step 4

// Get current directory
$dir = dirname(__FILE__);

ob_start(); // Start buffer
require_once($dir.'/pdf.php') // Include PDF layout file
$pdf_html = ob_get_contents(); // Get the contents of file into variable for later use
ob_end_clean(); // Close buffers

So now lets include dompdf and create the PDF

// Load the dompdf files
require_once($dir.'/dompdf/dompdf_config.inc.php');

$dompdf = new DOMPDF(); // Create new instance of dompdf
$dompdf->load_html($pdf_html); // Load the html
$dompdf->render(); // Parse the html, convert to PDF
$pdf_content = $dompdf->output(); // Put contents of pdf into variable for later

Pretty simple huh?

Step 6 - Creating HTML Email Template

We're going to be sending an HTML email, so we will create another very simple template to go along with the PDF in the email. Again, I'm keeping it simple for this tutorial and just Saying "Hello (name entered in form)", and then telling them to open the PDF that is attached.

html.php

<html>
<body>

<h1 style="font-family:Helvetica, Arial, sans-serif;">Hello <?php echo $post->name; ?></h1>
<p style="font-family:Helvetica, Arial, sans-serif;">Thanks for filling out the form. There should be a PDF attached to this message with your info. Check it out!</p>

</body>
</html>

And again, we'll have to start another output buffer to include the contents of our HTML email template with the included PHP.

// Get the contents of the HTML email into a variable for later
ob_start();
require_once($dir.'/html.php');
$html_message = ob_get_contents();
ob_end_clean();

Step 7 - Creating & Sending the Email

Now that we have our PDF and HTML template created and ready to be attached, let start the email message.

// Load the SwiftMailer files
require_once($dir.'/swift/swift_required.php');

$mailer = new Swift_Mailer(new Swift_MailTransport()); // Create new instance of SwiftMailer

$message = Swift_Message::newInstance()
->setSubject('How To Create and Send An HTML Email w/ a PDF Attachment') // Message subject
->setTo(array($post->email => $post->name)) // Array of people to send to
->setFrom(array('no-reply@coreyworrell.com' => 'Corey Worrell')) // From:
->setBody($html_message, 'text/html') // Attach that HTML message from earlier
->attach(Swift_Attachment::newInstance($pdf_content, 'coreyworrell.pdf', 'application/pdf')); // Attach the generated PDF from earlier

// Send the email, and show user message
if ($mailer->send($message))
$success = true;
else
$error = true;

Again, very simple with Swift Message. The code should explain itself, but the important parts are setBody() & attach()

We set the body of our message with our HTML template we created in step 6.
Then we add the PDF as an attachment ($pdf_content), set its name (coreyworrell.pdf), and the mime type (application/pdf).
It's as simple as that!

Then finally, send the email. If it works then set $success to true so we can show the appropriate message to the user. If something went wrong, then show error message.

The Result

Step 8 - Filling In the Missing Pieces

So now we have $success & $error defined, so we'll put those to use in our form layout. This will go above the form.

form.php

<?php if ($success) { ?>
<div class="message success">
<h4>Congratulations! It worked! Now check your email.</h4>
</div>
<?php } elseif ($error) { ?>
<div class="message error">
<h4>Sorry, an error occurred. Try again!</h4>
</div>
<?php } ?>

Final Code

Here's what form.php should look like at the end

<?php
if ( ! empty($_POST)) {

// Used for later to determine result
$success = $error = false;

// Object syntax looks better and is easier to use than arrays to me
$post = new stdClass;

// Usually there would be much more validation and filtering, but this
// will work for now.
foreach ($_POST as $key => $val)
$post->$key = trim(strip_tags($_POST[$key]));

// Check for blank fields
if (empty($post->name) OR empty($post->email) OR empty($post->about))
$error = true;

else {

// Get this directory, to include other files from
$dir = dirname(__FILE__);

// Get the contents of the pdf into a variable for later
ob_start();
require_once($dir.'/pdf.php');
$pdf_html = ob_get_contents();
ob_end_clean();

// Load the dompdf files
require_once($dir.'/dompdf/dompdf_config.inc.php');

$dompdf = new DOMPDF(); // Create new instance of dompdf
$dompdf->load_html($pdf_html); // Load the html
$dompdf->render(); // Parse the html, convert to PDF
$pdf_content = $dompdf->output(); // Put contents of pdf into variable for later

// Get the contents of the HTML email into a variable for later
ob_start();
require_once($dir.'/html.php');
$html_message = ob_get_contents();
ob_end_clean();

// Load the SwiftMailer files
require_once($dir.'/swift/swift_required.php');

$mailer = new Swift_Mailer(new Swift_MailTransport()); // Create new instance of SwiftMailer

$message = Swift_Message::newInstance()
->setSubject('How To Create and Send An HTML Email w/ a PDF Attachment') // Message subject
->setTo(array($post->email => $post->name)) // Array of people to send to
->setFrom(array('no-reply@coreyworrell.com' => 'Corey Worrell')) // From:
->setBody($html_message, 'text/html') // Attach that HTML message from earlier
->attach(Swift_Attachment::newInstance($pdf_content, 'coreyworrell.pdf', 'application/pdf')); // Attach the generated PDF from earlier

// Send the email, and show user message
if ($mailer->send($message))
$success = true;
else
$error = true;

}

}
?>

Final Words

Hopefully you now know more about dompdf and Swift Mailer and sending HTML emails with attachments.
Again, check out the source code in the demo or download the source to see exactly how everything works together.
And be sure to try out the demo and view the results of our hard work.


Comments

  • harry harry June 18, 2009

    useful post, thanks!

  • Robert Robert July 29, 2009

    What if I want the email attachment to go to a specific email all the time..How do i do that?

  • Corey Worrell Corey Worrell August 4, 2009

    @Robert:
    just replace line 51 in form.php with something like this:

    ->setTo(array('your_email@website.com' => 'Your Name'))

    and you could remove the email input field from the form also if you wanted.

  • Jan Jan August 21, 2009

    Hello!
    Thanks for your guide it had helped me great. But I have one problem: image doesn't render along with html. When PDF is not emailed, image is rendered normally. But when PDF is sent, image is not included. Using different image type (png/jpg/gif) makes no difference.
    I'm wondering if you can help me?
    Thanks in advance.

  • Corey Worrell Corey Worrell August 21, 2009

    @Jan:
    Glad it has helped you.
    When sending emails, all links and images must use the full URL.

    So instead of:
    <img src="images/photo.jpg" />

    Do this:
    <img src="http://mywebsite.com/images/photo.jpg" />

  • Jan Jan August 22, 2009

    Works like a charm:) Thank you for fast reply, article and help!

  • Peter Peter September 21, 2009

    Thanks for this great tutorial. I am planning to use this tutorial to build a online survey to get a pdf output. Is there a way to implement a function to check if required fields are not filled out without getting the error and the need of filling out te form again?

    Regards,

    Peter

  • Corey Worrell Corey Worrell September 21, 2009

    @Peter:
    Thanks, glad it could help.
    To answer your question, look over the form.php file, you can first remove lines 19 and 62 (the else statement). Then on line 16 you could change what happens when a field is empty.

  • Viktor Viktor November 17, 2009

    Hello,

    I need use charset windows-1260 in PDF file. Please help me!

    Viktor, Slovakia

  • Corey Worrell Corey Worrell November 17, 2009

    @Viktor:
    I'm pretty sure you can insert the meta tag with that charset in the head of your html document before converting it to a pdf.

    <meta http-equiv="content-type" content="text/html;charset=windows-1260">
  • Richard Richard November 29, 2009

    This is a great tutorial, it has been of help. And I've learned about Swift Mailer and dompdf. You just earned a subscriber!

  • Viktor Viktor December 5, 2009

    Hello!
    I need not only send but also open the PDF file with one click. I am beginner in PHP programming.
    Please help me and sorry for my English :)

    Viktor, Slovakia

  • Corey Worrell Corey Worrell December 5, 2009

    @Viktor:
    If you want to directly output the PDF to the user, you can use this function:

    $dompdf->stream('sample.pdf');
    
  • Viktor Viktor December 6, 2009

    Acrobat shows this errror message:

    Acrobat could not open "ziadost.pdf" because it is either not a supported file type or because the file has been damaged (for example, it was sent an email attachment and wasn´t correctly decoded).

    Where I must copy "$dompdf->stream..." command?

  • Corey Worrell Corey Worrell December 6, 2009

    @Viktor:
    Well if you want to email the pdf and have it open at the same time, you could put "$dompdf->stream..." inside the if statement if the email sent:

    if ($mailer->send($message))  {
    $success = true;
    $dompdf->stream('ziadost.pdf');
    }

    If that doesn't work then I'd take a look at the dompdf documentation.

  • Viktor Viktor December 6, 2009

    Doesn't work :(

    If you cannot solve this problem, it also helps when the script sends the PDF file to the address which I will fill-out in the form.

  • Viktor Viktor December 8, 2009

    The problem is with the webhosting, probably on Apache doesnt enabled some of functions.
    On other hosting script working fine!

    Thanks for your support ;)

  • Viktor Viktor December 8, 2009

    This is a solution for correct sending and opening at the same time

    // Load the dompdf files for opening
    require_once($dir.'/dompdf/dompdf_config.inc.php');
    
    $dompdf = new DOMPDF(); // Create new instance of dompdf
    $dompdf->load_html($pdf_html); // Load the html
    $dompdf->render(); // Parse the html, convert to PDF
    $dompdf->stream('ziadost.pdf');
    
    // Load the dompdf files for sending
    $dompdf = new DOMPDF(); // Create new instance of dompdf
    $dompdf->load_html($pdf_html); // Load the html
    $dompdf->render(); // Parse the html, convert to PD
    $pdf_content = $dompdf->output();// Put contents of pdf into variable for later
  • Nyoman Nyoman December 15, 2009

    Hi,
    I have document page document from : http://localhost/app/test.php?p=1_print&sid=0ms13cA5szja
    How to generate page to pdf and email it as attachment..?

    Thanks in advance
    Nyoman

  • Denise Denise May 25, 2010

    Hi

    I followed the tutorial and it works great. I am just wondering if I can allow users to attach other files. I need to convert the email like this does but I also want to let users attach other pdf files from their own computers. I have tried adjusting the code and it seems to work but Adobe won't open the second pdf. I get a message saying something like the file has been damaged and if it was sent by email attachment it wasn't correctly decoded.

    Any help greatly appreciated.

    Thanks

  • Corey Worrell Corey Worrell May 26, 2010

    Something like this should work:

    $message->attach(Swift_Attachment::newInstance($_FILES['user_uploaded_file']['tmp'], $_FILES['user_uploaded_file']['name'], 'application/pdf'));
  • Denise Denise May 26, 2010

    I am still getting the error when I try to open the second pdf. Where the code above says ['tmp'] I changed to ['tmp_name'] otherwise I got a error: Undefined index: tmp. Originally I had some code similar to this before the attach line of code:

    //Check if an attachment was uploaded
    $file_path = false;
    $file_name = false;
    $file_type = false;
    if (!empty($_FILES["attachment"]["tmp_name"]))
    {
    if ($_FILES["attachment"]["error"])
    {
    //Redirect if the upload has failed
    header("Location: ./form4.php?error=upload_failed");
    exit();
    }
    $file_path = $_FILES["attachment"]["tmp_name"];
    $file_name = $_FILES["attachment"]["name"];
    $file_type = $_FILES["attachment"]["type"];
    }

    So I tried swapping tmp_name with tmp but it made no difference and it does the same thing with or without this bit of code so at the moment I only have the line you suggested.

    This is the code I ended up with but it still does the same as it did before and won't open:

    $message->attach(Swift_Attachment::newInstance($_FILES['user_uploaded_file']['tmp_name'], $_FILES['user_uploaded_file']['name'], 'application/pdf'));

    I don't know a lot about this sort of thing, but I am wondering if it could be something to do with my form, this is what iI have:

    This is what I have at the beginning of form

    This is what I added for the attachment
    Attachment

    The rest of the form is like your tutorial.

    Thanks

  • Denise Denise May 26, 2010

    Sorry I should have put the code like this

    <form method="post" action="" enctype="multipart/form-data">
    <label for="user_uploaded_file"> Attachment</label>
    <input type="file" name="user_uploaded_file" id="user_uploaded_file" />
    
  • Corey Worrell Corey Worrell May 26, 2010

    Hmm.. I don't know what you mean by "it won't open". Email me from my contact form if you haven't figured it out yet.

  • Daniel Daniel June 25, 2010

    Hi foks,

    great tutorial, works awesome.

    Could you give me a hint on how to send the mail in TXT-format in addition?

    Thank you for your answers.

  • Corey Worrell Corey Worrell June 25, 2010

    @Daniel:
    Thanks! You can add this line after the 'setBody' line:

    ->addPart('The email message in plain text', 'text/plain');
  • Daniel Daniel June 25, 2010

    hey, great thanks for this... and I made another template called "plain_text.php" like you did above with the html-template:

    ob_start();
    require_once(TEMPLATEPATH.'/extra/plain_text.php');
    $txt_message = ob_get_contents();
    ob_end_clean();
    

    and then ...

    ->setFrom(array('mailer@mydomain.tld' => 'Subject text'))
    ->setTo(array($post->person_email => $post->person_lastname))
    ->attach(new Swift_MimePart($txt_message, 'text/plain'))
    ->attach(new Swift_MimePart($html_message, 'text/html'));
    

    I'm getting it now in both formats, but how can i manage to display my text-format-email with newlines ("\n") in Outlook or thunderbird. There are no new lines, all in one big line without line-breaks.

    I tried it with:

    <?php printf('\r\n') .... echo '\n'; ?>
    inside the "plain_text.php", but nothing works. And I double-checked that my outlook/thunderbird clients don't remove new lines: they don't.

    Corey, do you have a hint for me for solving this? best regards,
    Daniel

  • Corey Worrell Corey Worrell June 25, 2010

    In PHP, new lines only work if you use double-quotes

    <?php echo "\r\n"; ?>
  • Daniel Daniel June 26, 2010

    Hi Corey,

    that doesn't work, too... maybe it is a bug, read issues here:

    http://swiftmailer.lighthouseapp.com/projects/21527/tickets/113-quoted-printable-soft-line-breaks-problem#ticket-113-10

    http://swiftmailer.lighthouseapp.com/projects/21527/tickets/146-swift-406-multipart-html-txt-text-version-does-not-show-in-thunderbird#ticket-146-1

    Tried just to send the text email and see: there are new lines in the mail. Seems that we can't combine Text and HTML at the moment because of a bug.

    best regards,
    Daniel

  • Mary Mary June 28, 2010

    I am new at forms but what would be the easiest way to validate or at least make a field required and return that message at the top after submitted with an asterick next to the field?

  • Rick Rick July 7, 2010

    I have this working but for some reason the file attachments are empty or correpted.

    I am posting from a form and use the following line.

    ['tmp_name'];]
    ['name'];]
    ['type'];]
    
    
    

    The attachement is there but if I open the PDF in this case I get the following error..

    Acrobat could not open 'tes.pdf' because it is either not a supported file type or because the file has been damaged (for example, it was sent as an email attachment and wasn't correctly decoded)

    I have tried sending the email to other computers and they all get the same error.

    We are using Windows 2003 SBS with PHP 5.2.2 and IIS6

    If i open the PDF doc in word I see the actual path to the PHP temp DIR:
    C:\Program Files\PHP\temp\upload\php2F04.tmp

    can you please help with this....

  • Rick Rick July 7, 2010

    sorry code should be like this...

    $temp_name = $_FILES['attachment']['tmp_name'];
    $file_name = $_FILES['attachment']['name'];
    $file_type = $_FILES['attachment']['type'];
    $file_size = $_FILES['attachment']['size'];
    

    and adding the line

    ->attach(Swift_Attachment::newInstance($temp_name, $file_name, $file_type));

    to attache the file

  • harshad harshad July 27, 2010

    I m having PHP 5.1 . i have uploded all the files to server. when i submit the form it's giving an blank page. it's displaying error nicly. if i comment $dompdf->render(); this it's generating pdf file with 0 content. please suggest where it's compatible for PHP 5.1

  • Umanga Umanga August 23, 2010

    Hi this code working excelent .. thankz a lot ... i want to submit url name from form ... that should convert to the pdf and attached ......

    if i want to send url from form where should i change this code

  • shanthi shanthi August 26, 2010

    Hi I am new to php i have seen the demo and i just downloaded and tried in my server but its showing me an error Sorry, an error occurred. Try again!

    can u please guide me where the problem is!


Add Comment

  • (optional)