← PHP数据排序:ORDER BY子句 PHP面试常考程序题合集(含代码示例) →

PHP发送邮件:mail()函数

原创 2026-05-21 PHP 已有人查阅

网站开发里发邮件这个功能躲不开。用户注册要发验证邮件、下单要发订单通知、找回密码要发重置链接。PHP内置了一个mail()函数能干这事,不用装第三方库,开箱即用。

但有个事必须说在前头: mail()函数依赖服务器的邮件传输代理(MTA,Mail Transfer Agent)。你在自己电脑上装个XAMPP或者WAMP,默认没配邮件服务,直接跑mail()大概率发不出去。生产环境一般运维会配好postfix或者sendmail。

我以前踩过这个坑,本地测试折腾半天没反应,后来用SMTP配合PHPMailer才搞定。所以这节课讲mail()的用法,但实际项目中如果发邮件量大或者需要记录发送日志,可以考虑用第三方组件。

mail()函数语法

bool mail(string $to, string $subject, string $message, string $additional_headers, string $additional_parameters)

参数说明:

参数 作用 备注
$to 收件人 支持单人或多人,多个用逗号隔开
$subject 邮件主题 纯文本,不要换行
$message 邮件正文 每行不超70字符,用\r\n换行
$additional_headers 额外头信息 From、CC、BCC、MIME等
$additional_parameters 额外参数 传递给sendmail程序,一般不用

收件人写法:

user@example.com
user1@example.com, user2@example.com
张三 <zhangsan@example.com>
张三 <zhangsan@example.com>, 李四 <lisi@example.com>

最简单的文本邮件

<?php
// Windows服务器可能需要设置sendmail_from
ini_set("sendmail_from", "noreply@ebingou.cn");

$to = "alan@ebingou.cn";
$subject = "这是一封测试邮件";
$message = "你好,这是一条来自PHP mail()函数的纯文本消息。";
$headers = "From: system@ebingou.cn \r\n";

$result = mail($to, $subject, $message, $headers);

if ($result == true) {
    echo "邮件已发送";
} else {
    echo "邮件发送失败,请检查服务器邮件配置";
}
?>

收到的邮件内容:

From: system@ebingou.cn
To: alan@ebingou.cn
Subject: 这是一封测试邮件

你好,这是一条来自PHP mail()函数的纯文本消息。

个人经验: ini_set("sendmail_from", ...) 这行只在Windows服务器上有用。Linux服务器是在php.ini里配置sendmail_path。如果你不确定服务器环境,发不出去先问问运维。

用wordwrap()处理长文本

邮件协议要求每行不超过70字符。如果用mail()发几百字不带换行的内容,部分邮件服务器会出问题。

<?php
$long_message = "这是一段很长的文本内容。在PHP中使用mail()发送邮件时,如果一行文字超过了70个字符,可能会被某些邮件服务器截断或导致格式错乱。因此建议使用wordwrap()函数来统一处理换行。";

$wrapped_message = wordwrap($long_message, 70, "\r\n");

if (mail("alan@ebingou.cn", "长文本测试", $wrapped_message)) {
    echo "邮件发送成功";
} else {
    echo "邮件发送失败";
}
?>

wordwrap()的三个参数: 要处理的字符串、每行较大宽度、换行符用什么(\r\n是邮件标准)。

发送HTML格式邮件

纯文本太单调了,想加个样式、链接、图片什么的,得告诉邮件客户端按HTML解析。

<?php
$to = "alan@ebingou.cn";
$subject = "欢迎注册成功";
$message = "
    <div style='font-family:微软雅黑; padding:20px;'>
        <h2 style='color:#333;'>欢迎加入!</h2>
        <p>您的账号已注册成功,请点击下方链接激活:</p>
        <a href='https://www.ebingou.cn/activate?code=abc123'>激活账号</a>
        <hr>
        <small style='color:#999;'>如果按钮无法点击,请复制以下链接到浏览器:<br>https://www.ebingou.cn/activate?code=abc123</small>
    </div>
";

$headers = "From: service@ebingou.cn \r\n";
$headers .= "MIME-Version: 1.0 \r\n";
$headers .= "Content-type: text/html; charset=UTF-8 \r\n";

$result = mail($to, $subject, $message, $headers);

if ($result) {
    echo "HTML邮件已发送";
} else {
    echo "发送失败";
}
?>

关键点:

  • MIME-Version头告诉邮件客户端版本

  • Content-type: text/html 告诉客户端按HTML渲染

  • charset=UTF-8 保证中文不乱码

个人见解: 有些老旧的邮件客户端(比如Outlook 2007)对CSS支持有限,内联样式比外部样式更稳。写邮件HTML很好用table布局,别指望flex和grid。

发送带附件的邮件

这地方比较复杂。附件需要做base64编码,还要用multipart/mixed边界分隔正文和附件。

代码号学习编程示例:

<?php
$to = "alan@ebingou.cn";
$subject = "报表附件 - 月度销售数据";
$message = "您好,本月销售数据报表请见附件。";

// 要发送的文件路径(换成你自己的)
$file_path = "/tmp/monthly_report.pdf";
$file_name = "月度销售报表.pdf";

// 读取文件内容
$file_handle = fopen($file_path, "r");
if (!$file_handle) {
    die("无法打开文件");
}
$file_size = filesize($file_path);
$file_content = fread($file_handle, $file_size);
fclose($file_handle);

// Base64编码,每76字符加换行
$encoded_content = chunk_split(base64_encode($file_content));

// 生成唯一边界标识
$boundary = md5(time());

// 组装邮件头
$headers = "From: report@ebingou.cn\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: multipart/mixed; boundary=\"$boundary\"\r\n";

// 正文部分
$body = "--$boundary\r\n";
$body .= "Content-Type: text/plain; charset=UTF-8\r\n";
$body .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
$body .= "$message\r\n";

// 附件部分
$body .= "--$boundary\r\n";
$body .= "Content-Type: application/octet-stream; name=\"$file_name\"\r\n";
$body .= "Content-Transfer-Encoding: base64\r\n";
$body .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n\r\n";
$body .= "$encoded_content\r\n";
$body .= "--$boundary--";

// 发送
$result = mail($to, $subject, $body, $headers);

if ($result) {
    echo "带附件的邮件已发送";
} else {
    echo "发送失败";
}
?>

说一下这里的设计思路: multipart/mixed表示邮件由多个部分组成,每个部分用boundary隔开。boundary这个值要足够随机,不然可能和邮件正文内容撞车。用md5(time())产生的32位字符串基本够用了。

CC(抄送)用法

<?php
$to = "zhangsan@ebingou.cn";
$subject = "项目进度周报";
$message = "本周已完成数据库优化和缓存层搭建。";
$headers = "From: pm@ebingou.cn\r\n";
$headers .= "CC: lisi@ebingou.cn, wangwu@ebingou.cn";

if (mail($to, $subject, $message, $headers)) {
    echo "邮件已发送,已抄送李四和王五";
} else {
    echo "发送失败";
}
?>

CC和To的区别: CC的人能看到To是谁,大家都能看到完整的收件人列表。不像BCC。

BCC(密送)用法

<?php
$to = "manager@ebingou.cn";
$subject = "月报 - 请勿外传";
$message = "本月部门数据统计已完成,见附件。";
$headers = "From: hr@ebingou.cn\r\n";
$headers .= "BCC: boss@ebingou.cn";  // 老板秘密收到一份

if (mail($to, $subject, $message, $headers)) {
    echo "邮件已发送,密送副本已发出";
} else {
    echo "发送失败";
}
?>

实际场景: 给客户发邮件想抄送给内部同事,但又不想让客户看到内部邮箱,就用BCC。BCC接收者出现在邮件里,但To和CC的人都看不到。

多收件人发送

<?php
// 多个收件人用逗号分隔
$to = "user1@ebingou.cn, user2@ebingou.cn, user3@ebingou.cn";
$subject = "系统公告 - 临时维护通知";
$message = "服务器将于今晚22:00进行维护,预计2小时。";
$headers = "From: admin@ebingou.cn\r\n";

mail($to, $subject, $message, $headers);
?>

注意: 收件人太多或者频繁调用mail(),要考虑邮件服务器的发送频率限制。有的主机商一小时只允许发几十封。

邮件头格式规范

邮件头每行末尾必须是\r\n(回车+换行)。多个头信息拼在一起时,下一行直接拼接就行。

$headers = "From: sender@ebingou.cn\r\n";
$headers .= "Reply-To: support@ebingou.cn\r\n";
$headers .= "CC: cc@ebingou.cn\r\n";
$headers .= "BCC: bcc@ebingou.cn\r\n";
$headers .= "X-Priority: 3\r\n";  // 自定义头,非标准但很多客户端认

常用邮件头:

头名称 作用
From 发件人地址
Reply-To 回复地址(可不同于From)
CC 抄送
BCC 密送
Return-Path 退信接收地址
X-Mailer 声明用哪个程序发的(比如PHP)

常见问题和排查

邮件发不出去,先检查这几项:

  1. mail()返回false但不给原因 —— 看PHP错误日志,一般是sendmail路径不对

  2. 邮件进了垃圾箱 —— 缺少SPF记录,或者发件人域名没做邮件认证

  3. 中文乱码 —— 检查Content-Type有没有charset=UTF-8

  4. 附件收不到或打不开 —— 检查boundary边界字符串是否唯一,base64编码是否正确

检查邮件服务器状态的命令(Linux):

# 查看sendmail或postfix是否在运行
systemctl status sendmail
systemctl status postfix

# 看邮件队列
mailq

本节课程知识要点

  1. mail()依赖服务器MTA —— 本地测试发不出去是正常的,需要配置或用SMTP代替

  2. 每行不超过70字符 —— 长文本用wordwrap()处理,避免被截断

  3. HTML邮件必须加Content-Type头 —— text/html而不是text/plain

  4. 附件用base64编码 —— 配合multipart/mixed边界分隔

  5. 邮件头每行末尾是\r\n —— 不是\n也不是\r,标准的换行序列

  6. From头建议实域名 —— 很多邮件服务商会校验,写成@localhost容易被拒收

  7. 生产环境谨慎用mail() —— 大批量发邮件建议用SMTP + 第三方库(如PHPMailer或Symfony Mailer),可以记录发送日志、处理队列、避免被当成垃圾邮件源

实际项目中的取舍

mail()函数简单直接,但缺点也明显:

  • 没有发送失败后的重试机制

  • 不方便记录日志

  • 附件处理代码写起来麻烦

  • 依赖服务器配置,换个环境可能就不工作了

如果你的项目只是偶尔发几封通知邮件,比如用户注册验证,用mail()没问题。如果是电商订单提醒、每日报表这类需要保证送达率的场景,花点时间集成PHPMailer或SwiftMailer,支持SMTP、支持队列、支持日志,后期运维会省心很多。

← PHP数据排序:ORDER BY子句 PHP面试常考程序题合集(含代码示例) →
分享笔记 (共有 篇笔记)
验证码:
微信公众号