Sending Email with Attachments in PHP: A Comprehensive Guide
Sending emails with PHP is a common task for web developers, but adding attachments introduces additional complexity, such as handling file uploads, encoding binary data, and constructing a multipart email. This guide provides a detailed walkthrough of creating an HTML form and a robust PHP script to send emails with multiple file attachments securely. Whether you’re building a contact form or an automated email system, this tutorial will equip you with the knowledge to implement this feature effectively.
Creating the HTML Form
The HTML form is the user interface for collecting input, including text fields and file uploads. To handle file attachments, the form must use the multipart/form-data
encoding type, which allows binary data (like files) to be sent to the server. Below is a clean, accessible form with detailed explanations of each component.
Form Code
123456789101112131415161718192021<form enctype="multipart/form-data" method="POST" action="send_email.php"> <label for="sender_name">Your Name</label> <input type="text" id="sender_name" name="sender_name" required> <label for="sender_email">Your Email</label> <input type="email" id="sender_email" name="sender_email" required> <label for="subject">Subject</label> <input type="text" id="subject" name="subject" required> <label for="message">Message</label> <textarea id="message" name="message" rows="5" required></textarea> <label for="phone">Phone</label> <input type="tel" id="phone" name="phone" pattern="[0-9]{10,15}" required> <label for="my_files">Attachment(s)</label> <input type="file" id="my_files" name="my_files[]" multiple accept=".pdf,.doc,.docx,.jpg,.png"> <button type="submit" name="button">Submit</button> </form>
Form Explanation
- Encoding Type: The
enctype="multipart/form-data"
attribute is critical for file uploads, as it tells the browser to encode the form data in a way that supports binary files, unlike the defaultapplication/x-www-form-urlencoded
. - Input Fields:
- Name, Email, Subject: These are standard text inputs marked as
required
to ensure users provide them. Theemail
type enforces basic email format validation in modern browsers. - Message: A
<textarea>
allows multi-line input for the email body, withrows="5"
for better usability. - Phone: The
tel
input with apattern="[0-9]{10,15}"
ensures the phone number contains only digits and is between 10 and 15 characters long, covering most international formats. - File Input: The
name="my_files[]"
withmultiple
allows uploading multiple files. Theaccept
attribute restricts uploads to common file types (e.g., PDFs, Word documents, images) to guide users.
- Name, Email, Subject: These are standard text inputs marked as
- Accessibility: Each
<input>
and<textarea>
has an associated<label>
with afor
attribute matching the input’sid
, improving accessibility for screen readers. - Action: The form’s
action="send_email.php"
points to the PHP script that processes the submission.
PHP Script for Sending Email with Attachments
The PHP script handles form submissions, validates inputs, processes file uploads, and sends an email with attachments using PHP’s mail()
function. It includes robust validation, error handling, and security measures to ensure reliable operation.
PHP Code
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130<?php // Configuration $recipient_email = "[email protected]"; // Replace with the recipient's email address $from_email = "[email protected]"; // Replace with your domain's email address // Enable error reporting for development (disable in production) ini_set('display_errors', 1); error_reporting(E_ALL); // Check if the request is a POST submission if ($_SERVER["REQUEST_METHOD"] === "POST") { // Validate and sanitize inputs $sender_name = filter_input(INPUT_POST, "sender_name", FILTER_SANITIZE_STRING); if (empty($sender_name)) { exit("Error: Name is required."); } $sender_email = filter_input(INPUT_POST, "sender_email", FILTER_VALIDATE_EMAIL); if (!$sender_email) { exit("Error: Invalid email address."); } $phone_number = filter_input(INPUT_POST, "phone", FILTER_SANITIZE_NUMBER_INT); if (empty($phone_number) || !preg_match("/^[0-9]{10,15}$/", $phone_number)) { exit("Error: Phone number must be 10-15 digits."); } $subject = filter_input(INPUT_POST, "subject", FILTER_SANITIZE_STRING); if (empty($subject)) { exit("Error: Subject is required."); } $message = filter_input(INPUT_POST, "message", FILTER_SANITIZE_STRING); if (empty($message)) { exit("Error: Message is required."); } // Construct the message body $message_body = "Message from: $sender_name\n"; $message_body .= "Email: $sender_email\n"; $message_body .= "Phone: $phone_number\n"; $message_body .= "------------------------------\n"; $message_body .= "$message\n"; $message_body .= "------------------------------\n"; // Generate a unique boundary for multipart email $boundary = md5(uniqid(time())); // Set up email headers $headers = "MIME-Version: 1.0\r\n"; $headers .= "From: $from_email\r\n"; $headers .= "Reply-To: $sender_email\r\n"; // Initialize email body $body = ""; // Handle file attachments $attachments = $_FILES['my_files'] ?? []; $file_count = count($attachments['name'] ?? []); if ($file_count > 0) { // Set multipart/mixed content type for attachments $headers .= "Content-Type: multipart/mixed; boundary=\"$boundary\"\r\n"; // Add message part $body .= "--$boundary\r\n"; $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; $body .= "Content-Transfer-Encoding: base64\r\n\r\n"; $body .= chunk_split(base64_encode($message_body)); // Process each attachment for ($i = 0; $i < $file_count; $i++) { if ($attachments['error'][$i] === UPLOAD_ERR_OK && !empty($attachments['name'][$i])) { $file_name = $attachments['name'][$i]; $file_size = $attachments['size'][$i]; $file_type = $attachments['type'][$i]; $file_tmp = $attachments['tmp_name'][$i]; // Validate file size (max 5MB) if ($file_size > 5 * 1024 * 1024) { exit("Error: File '$file_name' exceeds 5MB limit."); } // Validate file type (optional, based on allowed types) $allowed_types = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'image/jpeg', 'image/png']; if (!in_array($file_type, $allowed_types)) { exit("Error: File '$file_name' has an unsupported type."); } // Read file content $content = file_get_contents($file_tmp); if ($content === false) { exit("Error: Failed to read file '$file_name'."); } // Encode file content $encoded_content = chunk_split(base64_encode($content)); // Add attachment part $body .= "--$boundary\r\n"; $body .= "Content-Type: $file_type; name=\"$file_name\"\r\n"; $body .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n"; $body .= "Content-Transfer-Encoding: base64\r\n\r\n"; $body .= "$encoded_content\r\n"; } elseif ($attachments['error'][$i] !== UPLOAD_ERR_NO_FILE) { $errors = [ UPLOAD_ERR_INI_SIZE => "File '$file_name' exceeds the upload_max_filesize directive in php.ini.", UPLOAD_ERR_FORM_SIZE => "File '$file_name' exceeds the MAX_FILE_SIZE directive in the HTML form.", UPLOAD_ERR_PARTIAL => "File '$file_name' was only partially uploaded.", UPLOAD_ERR_NO_FILE => "No file was uploaded.", UPLOAD_ERR_NO_TMP_DIR => "Missing a temporary folder.", UPLOAD_ERR_CANT_WRITE => "Failed to write file to disk.", UPLOAD_ERR_EXTENSION => "A PHP extension stopped the file upload." ]; exit($errors[$attachments['error'][$i]] ?? "Unknown upload error for '$file_name'."); } } $body .= "--$boundary--\r\n"; } else { // Plain text email without attachments $headers .= "Content-Type: text/plain; charset=UTF-8\r\n"; $body = $message_body; } // Send the email $sent = mail($recipient_email, $subject, $body, $headers); echo $sent ? "Thank you! Your email has been sent successfully." : "Error: Could not send email. Please check your PHP mail configuration."; exit; } ?>
PHP Code Explanation
- Configuration:
$recipient_email
and$from_email
define the recipient and sender email addresses. Replace these with your actual email addresses.- Error reporting is enabled for development to catch issues early. Disable
display_errors
in production for security.
- Input Validation:
- Uses
filter_input
withFILTER_SANITIZE_STRING
andFILTER_VALIDATE_EMAIL
to sanitize and validate inputs, preventing injection attacks. - The phone number is validated with a regex (
/^[0-9]{10,15}$/
) to ensure it contains only digits and meets length requirements. - Empty or invalid inputs trigger specific error messages and halt execution.
- Uses
- Message Body:
- Constructs a plain text message including the sender’s name, email, phone number, and message, separated by clear dividers for readability.
- File Handling:
- Checks for uploaded files via
$_FILES['my_files']
. - Validates file size (max 5MB) and type (PDF, Word, JPEG, PNG) to ensure compatibility and security.
- Uses
file_get_contents
for reliable file reading, replacing the less securefopen/fread
. - Encodes file content in base64 and splits it into chunks per RFC 2045 for email compatibility.
- Checks for uploaded files via
- Email Structure:
- Uses a unique
boundary
(generated withmd5(uniqid(time()))
) to separate message parts in a multipart email. - Sets MIME version, From, and Reply-To headers. For attachments, uses
multipart/mixed
; otherwise, usestext/plain
with UTF-8 encoding. - The message body is base64-encoded for compatibility with various email clients.
- Uses a unique
- Attachment Processing:
- Iterates through uploaded files, skipping those with errors (except
UPLOAD_ERR_NO_FILE
for optional uploads). - Adds each valid file as a separate part in the email body with appropriate headers (
Content-Type
,Content-Disposition
,Content-Transfer-Encoding
). - Closes the multipart email with a final boundary (
--boundary--
).
- Iterates through uploaded files, skipping those with errors (except
- Error Handling:
- Detailed upload error messages are mapped to PHP’s
UPLOAD_ERR_*
constants, providing user-friendly feedback. - File size and type validation prevent server overload or security risks.
- Detailed upload error messages are mapped to PHP’s
- Email Sending:
- The
mail()
function sends the email. Success or failure is reported to the user. - In production, consider using a library like PHPMailer for better reliability and features (e.g., SMTP support).
- The
Key Improvements Over the Original
- HTML Form:
- Added
id
attributes andfor
labels for accessibility. - Included
required
attributes and apattern
for phone validation. - Added an
accept
attribute to guide users on allowed file types. - Used a
<button>
instead of<input>
for the submit button for better styling flexibility.
- Added
- PHP Code:
- Replaced
$_POST
withfilter_input
for secure input handling. - Added file type validation to restrict uploads to safe formats.
- Implemented a 5MB file size limit to prevent server issues.
- Used
file_get_contents
for simpler, more reliable file reading. - Added UTF-8 encoding for better international support.
- Improved error messages with specific feedback for each validation failure.
- Structured the code with clear comments and modern PHP practices (e.g., strict comparison, early exits).
- Replaced
- Security:
- Sanitized all inputs to prevent injection attacks.
- Validated file types and sizes to mitigate security risks.
- Used a unique boundary for each email to avoid conflicts.
How to Use
- Set Up the Form:
- Save the HTML form in a file (e.g.,
index.html
) or integrate it into your website. - Ensure the form’s
action
attribute points tosend_email.php
.
- Save the HTML form in a file (e.g.,
- Configure the PHP Script:
- Save the PHP code as
send_email.php
in your server’s root or appropriate directory. - Update
$recipient_email
and$from_email
with your actual email addresses. - Verify that your server has PHP’s
mail()
function enabled or configured with an SMTP server.
- Save the PHP code as
- Test the Form:
- Upload the files to your server and access the form via a browser.
- Test with and without attachments to ensure both plain and multipart emails work.
- Check your server’s error logs if the email fails to send.
- Production Considerations:
- Disable
display_errors
in production to avoid exposing sensitive information. - Consider using a library like PHPMailer or SwiftMailer for robust email delivery, especially for high-volume or SMTP-based sending.
- Adjust the
upload_max_filesize
andpost_max_size
inphp.ini
if you need to allow larger files.
- Disable
- Optional Enhancements:
- Add client-side validation with JavaScript for instant feedback.
- Implement an Ajax-based submission to avoid page reloads (see resources for Ajax tutorials).
- Add a CAPTCHA to prevent spam submissions.
Troubleshooting Tips
- Email Not Sending: Ensure your server’s
mail()
function is configured correctly. Check the server’s mail log (e.g.,/var/log/maillog
) for errors. Alternatively, switch to PHPMailer with SMTP settings. - File Upload Issues: Verify that
upload_max_filesize
andpost_max_size
inphp.ini
are sufficient. Ensure the temporary upload directory is writable. - Invalid File Types: Adjust the
$allowed_types
array in the PHP code to include other file types as needed. - Email Formatting Issues: Test with multiple email clients (e.g., Gmail, Outlook) to ensure attachments and message body display correctly.
Conclusion
This PHP email script provides a reliable, secure way to send emails with attachments from a web form. By following best practices for input validation, file handling, and email construction, you can integrate this solution into your projects with confidence. For advanced features, such as asynchronous form submission, explore Ajax-based implementations. Experiment with this code, and let us know your results in the comments!
Call to Action: Have you used PHP’s mail()
function for attachments before? Share your tips or questions below!