Question
I created a simple contact form on index.html that submits to a separate thankyou.php page. The PHP code runs and the form submits successfully, but no email is actually sent.
Here is the code:
<?php
$name = $_POST['name'];
$email = $_POST['email'];
$message = $_POST['message'];
$from = 'From: yoursite.com';
$to = 'contact@yoursite.com';
$subject = 'Customer Inquiry';
$body = "From: $name\nE-Mail: $email\nMessage:\n$message";
if ($_POST['submit']) {
if (mail($to, $subject, $body, $from)) {
echo '<p>Your message has been sent!</p>';
} else {
echo '<p>Something went wrong, please go back and try again.</p>';
}
}
?>
Why would the form submit correctly but the email never be sent, and how can this be fixed?
Short Answer
By the end of this page, you will understand how PHP's mail() function works, why a form can submit successfully even when no email is delivered, and how to fix common issues such as missing server mail configuration, invalid headers, unchecked form input, and weak validation.
Concept
PHP's mail() function only asks the server to hand off an email. It does not guarantee that the email will actually arrive in the inbox.
That is the key idea behind this problem.
A contact form can appear to work because:
- the browser successfully sends a POST request
thankyou.phpruns without a syntax errormail()returnstrueif the server accepts the handoff request
But email delivery can still fail later because of:
- missing mail server configuration on the hosting environment
- invalid
Fromheaders - spam filtering
- incorrect recipient address
- server restrictions on sending mail
- unvalidated or missing form fields
In your example, there are two separate concerns:
- Form submission: the user sends data to
thankyou.php - Email delivery: the server must be configured to send mail
If the first works but the second does not, the problem is usually not the HTML form itself. It is often one of these:
- the server does not support
mail() - the
Fromheader is not formatted correctly - the email is being blocked or marked as spam
- the script does not validate the request before sending
A safer and cleaner version usually includes:
- checking that the request method is
POST - validating required fields
- sanitizing user input
- using a proper
From:header with a real email address - optionally adding a
Reply-To:header - checking server mail configuration
Example:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$message = trim($_POST['message'] ?? '');
if ($name && $email && $message && filter_var($email, FILTER_VALIDATE_EMAIL)) {
$to = 'contact@yoursite.com';
$subject = 'Customer Inquiry';
$body = "From: $name\nEmail: $email\nMessage:\n$message";
$headers = "From: no-reply@yoursite.com\r\n";
$headers .= "Reply-To: $email\r\n";
if (mail($to, $subject, $body, $headers)) {
echo 'Your message has been sent!';
} else {
echo 'Mail could not be sent.';
}
} else {
echo 'Please fill in all fields with a valid email address.';
}
}
?>
This matters in real programming because sending email is a server-side integration task, not just a PHP syntax task. Working code still depends on environment configuration and correct formatting.
Mental Model
Think of mail() like handing a letter to a mailroom clerk.
- Your HTML form is the person writing the letter.
- PHP builds the envelope and contents.
mail()gives it to the server's mail system.- The mail server tries to deliver it.
If the clerk accepts the letter, your script may think everything is fine. But the letter can still fail later if:
- the address is wrong
- the envelope is badly labeled
- the mailroom is closed
- the letter is rejected by the receiving building
So mail() is not the same as "the email arrived." It is closer to "the server attempted to send it."
Syntax and Examples
The basic syntax of mail() is:
mail($to, $subject, $message, $headers);
Parameters
$to: recipient email address$subject: email subject line$message: message body$headers: optional headers such asFromandReply-To
Beginner-friendly example
<?php
$to = 'contact@example.com';
$subject = 'Test Email';
$message = "Hello, this is a test email.";
$headers = "From: no-reply@example.com\r\n";
if (mail($to, $subject, $message, $headers)) {
echo 'Email accepted for sending.';
} else {
echo 'Email sending failed.';
}
?>
Contact form example
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$message = trim($_POST['message'] ?? '');
if (!$name || !$email || !$message) {
echo 'All fields are required.';
exit;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo 'Please enter a valid email address.';
exit;
}
$to = 'contact@yoursite.com';
$subject = 'Customer Inquiry';
$body = "Name: $name\nEmail: $email\n\n$message";
$headers = "From: no-reply@yoursite.com\r\n";
$headers .= "Reply-To: $email\r\n";
if (mail($to, $subject, $body, $headers)) {
echo 'Your message has been sent!';
} else {
echo 'The server could not send your email.';
}
}
?>
Why this version is better
- It checks that the page was requested with
POST - It validates empty fields
- It validates the user's email format
- It uses a proper
Fromaddress - It adds
Reply-Toso you can respond to the sender
Step by Step Execution
Consider this example:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = trim($_POST['email'] ?? '');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo 'Invalid email';
exit;
}
$sent = mail(
'contact@example.com',
'Hello',
'Test message',
"From: no-reply@example.com\r\nReply-To: $email\r\n"
);
if ($sent) {
echo 'Sent';
} else {
echo 'Failed';
}
}
?>
What happens step by step
- The browser submits the form using
POST. - PHP checks
$_SERVER['REQUEST_METHOD'] === 'POST'. - The script reads the
emailfield from$_POST. trim()removes extra spaces.filter_var(..., FILTER_VALIDATE_EMAIL)checks whether the email looks valid.- If invalid, the script prints
Invalid emailand stops withexit. - If valid, PHP calls
mail(). - The server tries to hand the message to its mail system.
mail()returnstrueorfalse.- The script prints
SentorFailed.
Important detail
Even if mail() returns true, the message can still be:
- delayed
- filtered into spam
- rejected by the receiving mail server
That is why debugging email often requires checking both the PHP code and the server setup.
Real World Use Cases
Email sending from PHP forms is common in many real applications.
Contact forms
A visitor fills out a contact form, and the site owner receives the message by email.
Support request forms
A customer sends a problem report, and the support team gets notified.
Account notifications
A system sends password reset links, welcome emails, or verification emails.
Admin alerts
A website emails administrators when errors, payments, or new registrations happen.
Internal business tools
A company dashboard can send approval requests, status updates, or reminders.
In all of these cases, developers must think about more than just calling mail():
- Is the mail server configured?
- Are email headers valid?
- Are addresses verified?
- Could messages be marked as spam?
- Should a library or SMTP service be used instead?
Real Codebase Usage
In real projects, developers usually wrap email logic in safer patterns rather than writing everything inline.
Common patterns
Validate before sending
Check required fields first.
if (empty($name) || empty($email) || empty($message)) {
exit('Missing required fields.');
}
Use guard clauses
Stop early when input is bad.
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
exit('Invalid email address.');
}
Use a server-owned From address
Many mail servers reject messages that pretend to come from arbitrary user emails.
$headers = "From: no-reply@yoursite.com\r\n";
$headers .= "Reply-To: $email\r\n";
Separate form handling from display
Developers often keep email logic in a dedicated handler file or controller.
Log failures
Instead of only showing a user-facing message, real apps log failures for debugging.
if (!mail($to, $subject, $body, $headers)) {
error_log('Mail sending failed.');
}
Prefer libraries in production
In larger applications, developers often use tools like PHPMailer or Symfony Mailer because they support:
- SMTP authentication
- better error reporting
- HTML email
- attachments
- safer header handling
For learning, mail() is useful. For production, a library is often more reliable.
Common Mistakes
Here are some frequent mistakes beginners make when using mail().
1. Assuming form submission means email delivery
A successful page submission does not prove the email was sent.
2. Using an invalid From header
Broken example:
$from = 'From: yoursite.com';
This is not a proper email address. A better version is:
$headers = "From: no-reply@yoursite.com\r\n";
3. Not validating the request method
Broken example:
if ($_POST['submit']) {
// send mail
}
Problems:
submitmight not exist- this can cause notices
- it depends on a specific form field name
Better:
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// send mail
}
4. Not validating user input
Broken example:
$email = $_POST['email'];
Safer:
$email = trim($_POST['email'] ?? '');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
exit('Invalid email');
}
5. Forgetting server configuration
If PHP is running on a local machine or an unconfigured server, mail() may do nothing useful.
You may need:
- a configured sendmail service on Linux
- SMTP or mail server setup on Windows
- hosting support for PHP mail
6. Ignoring spam folders
Sometimes the email was sent, but it lands in spam.
7. Header injection risks
Never blindly place raw user input into headers.
Broken example:
$headers = "From: $email\r\n";
If not sanitized, malicious input could break headers. A safer approach is to use your own domain for From and user email only in Reply-To after validation.
8. Not checking destination email correctness
A typo in the recipient address means the message cannot be delivered.
$to = 'contact@yoursite.com';
Comparisons
Here is how mail() compares with related approaches.
| Approach | Best for | Pros | Cons |
|---|---|---|---|
mail() | Simple learning examples, basic hosting | Built into PHP, easy to start | Weak debugging, depends on server config |
| PHPMailer | Real contact forms and production apps | SMTP support, better errors, more features | Requires installation |
| Symfony Mailer | Modern PHP applications | Clean architecture, strong framework support | More setup than mail() |
| API-based email services | High deliverability apps | Reliable delivery, analytics, templates | Requires external service |
$_POST['submit'] vs request method check
| Technique | Meaning | Better choice? |
|---|---|---|
isset($_POST['submit']) | Checks whether a specific submit field was posted | Sometimes useful |
$_SERVER['REQUEST_METHOD'] === 'POST' | Checks whether the request is a form POST | Usually better |
From vs Reply-To
| Header | Purpose | Typical value |
|---|---|---|
From | The sending address used by the server | no-reply@yoursite.com |
Reply-To | Where replies should go | the user's validated email |
For contact forms, this combination is common:
From: your domain emailReply-To: the visitor's email
Cheat Sheet
mail($to, $subject, $message, $headers);
Safe contact form pattern
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$message = trim($_POST['message'] ?? '');
if (!$name || !$email || !$message) exit('All fields are required.');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) exit('Invalid email.');
$to = 'contact@yoursite.com';
$subject = 'Customer Inquiry';
$body = "Name: $name\nEmail: $email\n\n$message";
$headers = "From: no-reply@yoursite.com\r\n";
$headers .= "Reply-To: $email\r\n";
if (mail($to, $subject, $body, $headers)) {
echo 'Sent';
} else {
echo 'Failed';
}
}
?>
Rules to remember
mail()does not guarantee inbox delivery- Use
POSTchecks before reading form data - Validate emails with
filter_var() - Use a real email address in
From - Prefer
Reply-Tofor the user's address - Check spam folders
- Make sure the server is configured to send mail
- Consider PHPMailer or SMTP for production
Common trouble spots
- invalid
Fromheader - hosting blocks outgoing mail
- local machine not configured for email
- recipient address typo
- message flagged as spam
FAQ
Why does PHP mail() return true but no email arrives?
mail() returning true usually means the server accepted the request to send. It does not guarantee final delivery to the inbox.
Does mail() work on localhost?
Usually not without extra configuration. Your local machine needs a mail server or SMTP setup.
What should I put in the From header?
Use a real email address from your own domain, such as no-reply@yoursite.com.
Should I use the visitor's email in From?
Usually no. Use it in Reply-To after validating it.
Why is checking $_POST['submit'] not ideal?
It depends on a specific form field existing. Checking $_SERVER['REQUEST_METHOD'] === 'POST' is more reliable.
How do I validate an email address in PHP?
Use filter_var($email, FILTER_VALIDATE_EMAIL).
Why is my email going to spam?
Common reasons include weak headers, missing domain authentication, suspicious server setup, or generic message content.
Is mail() good for production apps?
For small setups it can work, but many production apps use SMTP libraries or email APIs for better reliability.
Mini Project
Description
Build a small PHP contact form handler that validates input and attempts to send an email safely. This project demonstrates the difference between receiving form data and actually preparing a valid email request for the server.
Goal
Create a working PHP mail handler that validates form input, uses proper headers, and gives clear success or failure messages.
Requirements
- Accept
name,email, andmessagefrom a POST form. - Reject the request if any field is empty.
- Validate that the email address has a correct format.
- Use a domain-owned
Fromheader and aReply-Toheader. - Show a clear message for success or failure.
Keep learning
Related questions
Are PDO Prepared Statements Enough to Prevent SQL Injection in PHP?
Learn how PDO prepared statements prevent SQL injection in PHP, what they protect, and the mistakes that still leave MySQL apps vulnerable.
Can You Bind an Array to an IN Clause in PHP PDO?
Learn how PDO handles placeholders in IN() clauses, why arrays cannot be bound directly, and the safe PHP pattern to build dynamic queries.
Choosing the Right MySQL Collation for PHP and UTF-8
Learn how MySQL character sets and collations work with PHP, and how to choose a practical UTF-8 setup for web applications.