For production PHP applications, use PHPMailer, Symfony Mailer, or Laravel's Mail component over an authenticated SMTP relay or provider API — never PHP's built-in mail() function. mail() gives no authentication, no bounce handling, and no delivery feedback. Configure SPF and DKIM on the sending domain, use a managed sender (SendGrid, Postmark, Mailgun, SES), and handle webhooks for bounces and complaints.
Sending Email With PHP: API and SMTP Approaches
PHP's built-in mail() function still works the same way it did in 2003 — and that's the problem. Modern email delivery requires authentication, DKIM signing, bounce processing, and managed IP reputation. The mail() function does none of that. In 2026, sending production email from PHP means using PHPMailer, Symfony Mailer, or Laravel's Mail facade over a managed sender's SMTP relay or API.
The cluster around "email api php" and "mailer code" reflects the messy state of PHP email tutorials online — half are 2008 mail() snippets that don't work for Gmail anymore. This guide covers what actually works in 2026 for sending email from PHP applications.
Why mail() Is Wrong For Production
The built-in function hands your message to the local MTA (sendmail, Postfix, exim) and returns a boolean. Problems:
- No authentication. No SPF alignment, no DKIM signing, no domain verification.
- No bounce handling. When a recipient rejects, you find out maybe via the local mail log, maybe not at all.
- No delivery feedback. Did Gmail accept it? Spam folder? You don't know.
- Bad shared IP. Your VPS provider's IP is on every blacklist.
- Rate limited. Most VPS providers throttle outbound port 25 and Gmail/Outlook will tarpit you.
Anyone running mail() to Gmail in 2026 has a near-100% spam folder rate. See why emails go to spam for the broader context.
The Production Stack
- PHP library — PHPMailer, Symfony Mailer, or Laravel Mail
- Managed sender — SendGrid, Postmark, Mailgun, SES, or Resend (provides authenticated SMTP relay + API)
- Domain authentication — SPF, DKIM, DMARC configured on your sending domain
- Webhook handler — receive bounces, complaints, and update suppression list
PHPMailer: The Defaults
PHPMailer is the most widely used library. Install:
composer require phpmailer/phpmailer
Send via Postmark's SMTP relay:
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
$mail = new PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = 'smtp.postmarkapp.com';
$mail->SMTPAuth = true;
$mail->Username = getenv('POSTMARK_TOKEN');
$mail->Password = getenv('POSTMARK_TOKEN');
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
$mail->setFrom('[email protected]', 'Example App');
$mail->addAddress('[email protected]');
$mail->isHTML(true);
$mail->Subject = 'Order confirmation';
$mail->Body = $htmlBody;
$mail->AltBody = $textBody;
$mail->send();
} catch (Exception $e) {
error_log("Mail failed: {$mail->ErrorInfo}");
}
This works for all major providers — swap the host, username, and password.
Symfony Mailer
For Symfony or framework-agnostic projects, Symfony Mailer is more modern:
composer require symfony/mailer
composer require symfony/mailgun-mailer # provider-specific transport
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\Transport;
use Symfony\Component\Mime\Email;
$transport = Transport::fromDsn('mailgun+api://KEY:DOMAIN@default');
$mailer = new Mailer($transport);
$email = (new Email())
->from('[email protected]')
->to('[email protected]')
->subject('Welcome')
->html($htmlBody)
->text($textBody);
$mailer->send($email);
Symfony Mailer has transports for SendGrid, Mailgun, Postmark, SES, Mailchimp, Sendinblue/Brevo, and others. Switch by changing the DSN.
Laravel Mail
Laravel wraps Symfony Mailer with a cleaner API:
// config/mail.php uses MAIL_MAILER env var
// .env: MAIL_MAILER=postmark, POSTMARK_TOKEN=xxx
use Illuminate\Support\Facades\Mail;
Mail::to($user->email)->send(new OrderConfirmation($order));
Laravel has built-in support for SMTP, SendGrid, Mailgun, Postmark, SES, and Resend. Queueing (->queue() instead of ->send()) handles rate limiting and retries.
Provider SDKs (When SMTP Isn't Enough)
For features beyond send — templates, batch, suppression management — use the provider's PHP SDK:
| Provider | Composer package |
|---|---|
| SendGrid | sendgrid/sendgrid |
| Postmark | wildbit/postmark-php |
| Mailgun | mailgun/mailgun-php |
| Resend | resend/resend-php |
| AWS SES | aws/aws-sdk-php |
| Mailersend | mailersend/mailersend |
The SDKs expose features SMTP doesn't — batch endpoints, template variables, suppression list management, inbound parsing.
DKIM Signing in PHP
If you're sending via a managed provider, they sign DKIM for you. You just add a CNAME to your DNS pointing to their selector. See DKIM setup guide.
If you must sign in PHP (self-hosted MTA, direct SMTP), PHPMailer supports it:
$mail->DKIM_domain = 'example.com';
$mail->DKIM_private = '/path/to/private.key';
$mail->DKIM_selector = 'phpmailer';
$mail->DKIM_passphrase = '';
$mail->DKIM_identity = $mail->From;
Then publish the public key at phpmailer._domainkey.example.com as a TXT record. See DKIM key rotation for rotation practices.
Practitioner note: I see PHP apps with DKIM keys committed to the repo regularly. Don't. Store the private key in a secrets manager or read it from a file outside web root. A leaked DKIM private key requires immediate selector rotation across all senders using your domain.
Webhook Handlers for Bounces
The provider POSTs bounce events to your webhook endpoint. Build a controller that:
- Verifies the webhook signature (provider-specific HMAC or signature header)
- Parses the event type (bounce, complaint, delivered, opened, etc.)
- Updates the suppression table in your database for bounces and complaints
Example for Postmark in Laravel:
Route::post('/webhooks/postmark', function (Request $request) {
// verify basic auth on the endpoint
$event = $request->all();
if ($event['RecordType'] === 'Bounce' && $event['Type'] === 'HardBounce') {
Suppression::firstOrCreate([
'email' => $event['Email'],
'reason' => 'hard_bounce',
]);
}
return response('', 200);
});
Practitioner note: Wildcard email aliases in PHP apps cause weird bounce storms. If
[email protected]forwards to[email protected]and helpdesk bounces, the original sender (your app) gets the bounce attributed. Configure bounce processing to handle forwarded bounces — most providers do this automatically but it's worth verifying.
What to Avoid
- Don't use
mail()for anything production. - Don't put SMTP credentials in code. Environment variables or secrets manager.
- Don't send from Gmail SMTP for app email. Hard rate limits, not designed for it.
- Don't ignore webhooks. You will mail to bounced addresses and burn reputation.
- Don't share a "noreply@" address across transactional and marketing. Separate subdomains.
If you need help wiring up a PHP application's email infrastructure — provider choice, authentication, bounce handling — book a consultation. I do PHP-stack email setups for ecommerce, agencies, and SaaS teams running Laravel, Symfony, and custom applications.
Sources
- PHPMailer on GitHub
- Symfony Mailer Documentation
- Laravel Mail Documentation
- Postmark PHP Library
- Mailgun PHP SDK
- PHP mail() Manual
v1.0 · May 2026
Frequently Asked Questions
How do I send email in PHP?
Install PHPMailer (composer require phpmailer/phpmailer) or use the framework's mailer (Symfony Mailer, Laravel Mail). Configure SMTP credentials from a managed sender like SendGrid, Postmark, or Mailgun. Set SPF and DKIM on the sending domain. The built-in mail() function works for prototypes only.
What is the best email API for PHP developers?
SendGrid, Postmark, Mailgun, and Resend all have first-class PHP SDKs via Composer. Postmark for transactional deliverability. Mailgun for high volume and EU residency. SendGrid for hybrid marketing + transactional. AWS SES if you're already AWS-native.
Is PHP mail() function still used?
It's still installed everywhere but it shouldn't be used in production. mail() relies on the local MTA (sendmail, Postfix) and gives no authentication, no DKIM signing, no bounce handling, and no delivery confirmation. Almost everything sent via mail() goes to spam at Gmail and Outlook.
How do I sign DKIM in PHP?
Use PHPMailer's DKIM_sign method or Symfony Mailer's DKIM signer. Easier: send via a managed provider (SendGrid, Postmark, Mailgun, SES) and let the provider sign DKIM with their managed key, plus add a DNS CNAME for your domain-aligned key.
What is PHPMailer?
PHPMailer is the most widely used PHP library for sending email. It supports SMTP, attachments, HTML email, DKIM signing, and OAuth2. Maintained on GitHub, MIT-licensed, used by WordPress, Drupal, Joomla, and millions of custom applications.
Want this handled for you?
Free 30-minute strategy call. Walk away with a plan either way.