详解PHP发送邮件知识点


Posted in PHP onMay 06, 2018

发送邮件是网站的常用功能,用户激活、找回密码等场景常需要发送邮件到用户邮箱。本文先回顾发送邮件的相关概念,再给出使用PHP发送邮件的示例代码。

发送短信

从功能上看,短信和邮件类似,用途常是通知和安全校验。发送短信(基本上)需要向供应商付费,所以短信供应商有动力提供清晰的文档,易用的接口方便用户接入。一般而言,发送短信的是:

寻找供应商,例如阿里大鱼、聚合数据等;

注册账户,获取appid和appkey;

申请模板;

查看接口文档,集成到应用中;

调用API发送短信。

流程简单易懂,接入和使用也十分便捷,基本上一两小时内就能对接和测试好。用户无需考虑讯息在通讯过程中的编码、寻址下发等细节,缺点是要付费。

邮件一般是免费服务,相关支持没那么到位,这也要理解。各种编程语言发送邮件的类库不少,从信源角度看基本可以分成两类:从本机发送和从第三方邮件服务商发送。为了理解邮件发送的流程,先介绍一些相关概念。

相关概念

大部分接触到互联网的人都有使用邮件的经验,但基本上限于邮件客户端、网页端和提供商这几个概念。作为一个开发,理解本节中的以下概念能更好的帮你掌握邮件通讯中的细节。

MUA : Mail User Agent,邮件用户代理。用户代理是开发中经常接触到的词,主要指 理解人的意图并代表用户向资源方请求的工具。例如浏览器是最常用的用户代理,以HTTP/HTTPS协议格式向web服务器发送请求,并解析响应,渲染后呈现给用户。邮件用户代理,常见的是Foxmail、Outlook这类工具,人们写好邮件后,按格式封装邮件内容与邮件服务器通讯。

MTA : Mail Transfer Agent,邮件传输代理,帮用户收发邮件的程序。常说的邮件服务器指的就是MTA,开源的程序有sendmail,postfix,QMail等。

MRA : Mail Retrieval Agent,邮件收取代理,将用户的邮件从邮件服务器取回本地。邮件客户端是常见的MRA。

SMTP : Simple Mail Transfer Protocol,简单邮件传输协议。用户与邮件服务器、邮件服务器互相传递邮件均使用该协议(默认明文,可使用SSL\TLS加密)。

POP3/IMAP : Post Office Protocol version 3/Internet Message Access Protocol,邮局协议版本3或网络信息获取协议,客户端从服务端获取邮件时使用的协议。

用户A(163邮箱)向用户B(Gmail邮箱)发信,用户B获取信件的过程涉及到上述的概念。流程和概念关系可用如下简图表示:

用户A --发送邮件--> 用户B
 M|S         M|I
 U|M         R|M
 A|T         A|A
 |P         |P
 v          v
MTA(163)--转发(SMTP)->MTA(gmail)

注:上图给出的是邮件发送的大体流程,其他MSA、MDA、ESMTP、SMTPS等可能会出现在整个流程中,但不影响邮件收发的理解。下文中会提到的缩写和概念会注明,其他请自行查询。

postfix

Linux下发送邮件的软件主要是sendmail和postfix,它们在系统中充当上文概念中的MTA/MDA(Mail Delivery Agent,邮件投递代理)角色。它帮助用户向外发送邮件,接收邮件投递到用户信箱(默认位置/var/spool/mail/用户名)。

sendmail是老牌的邮件软件,知名度非常高。但是Wietse(Wietse Zweitze Venema)用的不爽,于是有了postfix。postfix命令(几乎)兼容于sendmail,但更高效和安全(后缀fix的由来),是目前大部分Linux发行版的默认邮件收发软件,推荐使用postfix而非sendmail(本博客多年前有篇文章写如何配置sendmail,那时年少无知见识少,打算抽空把那篇文章改一下)。

postfix的主要配置文件是/etc/postfix/main.cf,配置文件的注释非常全,选项基本是自解释的。最重要的几个配置是:myhostname、myorigin、inet_interfaces、inet_protocols以及mydestination(如果你打算收外网来信的话)。需要注意inet_interfaces配置为localhost时,inet_protocols的值应为ipv4,否则可能会出现类似postfix: fatal: parameter inet_interfaces: no local interface found for ::1的错误提示。

与邮件相关的几个常用postfix命令是:

mail或mailx,发送邮件。tlanyan用户向root发送邮件:mail -s "Greetings" root@localhost -r tlanyan@localhost,接着终端中输入A nice day!,然后回车,按ctrl+D结束正文编辑,邮件就已经发送出去。登录到root账号,会提示在/var/spool/mail/root中有新邮件。用tail或者其他命令可查看邮件的详细信息。

postquque,查看邮件发送队列。postqueue -p可取代sendmail中的mailq命令,postqueue -f刷新队列(强制尝试发送队列中的邮件)。

postcat,查看未发送邮件的信息。例如postcat -q xxxx(xxxx是postqueue或者mailq显示的未发送队列ID)可查看邮件的详细信息,postcat -b -q xxxxx只查看邮件正文。

postsuper,超级用户才可使用的邮件管理程序。postsuper -d xxxx,删除队列ID为xxxxx的邮件;postsuper -h xxxxx,暂停队列ID为xxxx的邮件发送,等。

以上介绍对于发送邮件基本已足够。注意,mail命令发送的邮件能投递的前提是postfix正在运行(ps aux | grep postfix | grep -v grep输出不为空)。

有了postfix,配置好后可以对外发送邮件,也能收取外网发送过来的邮件,但限于命令行操作。想用foxmail等客户端收发邮件,需要让服务器支持POP3/IMAP协议。开源的dovecot可以实现这个功能。dovecot服务于收邮件而非发送,了解其对开发中的帮助不大。如果想搭建一套完整的邮件系统(包括网页端支持、垃圾邮件过滤、病毒查杀、传输加密等),建议参考或使用国产开源的 EwoMail。

了解postfix对开发中发送邮件帮助有多大?说实话,几乎没有帮助。原因是为了防止垃圾邮件泛滥,各大云服务器厂商屏蔽了25端口(Google Cloud连465都干掉了)。亚马逊云通过申请还有放行的可能(但有速率和每日额度限制),其他厂商几乎不会让你使用自己的域名从本机直接发送邮件。封禁25端口,必须使用第三方的邮件服务,几乎是业界的标准做法。

聪明的人可能想到,使用465加密端口(基于SMTPS,SMTP over SSL协议)或587端口(SMTP over STARTTLS协议)发送邮件,是不是就能绕开限制了?阿里云/腾讯云等厂商并不封禁465端口,发送邮件可以使用该端口而无需申请。但注意465和587端口是客户端和邮件服务器通讯使用的端口,邮件服务器之间通讯使用25端口。你可以通过465端口连接到Gmail邮箱对外发送邮件,但无法让postfix使用465端口投递邮件到hotmail邮件服务器。

总结来说,sendmail/postfix作为垃圾和欺诈邮件泛滥前的邮件服务器软件,对业界贡献很大。随着云服务器的盛行,几乎无法以指向本机的域名向外发送邮件,sendmail/postfix除了在本机内发送提醒邮件,用处已然不大。要对外发送邮件,要么自建机房,要么使用第三方邮件系统。

PHP的mail函数

作为PHP开发中,了解sendmail/postfix还是有点用处。mail函数默认使用sendmail/postfix发送邮件,了解相关配置,就能知道为啥能工作/为啥不能工作。

简单来说,要让PHP自带的mail函数正常工作,需要做以下事情:

申请域名,在DNS解析中设置MX记录,指向本机(非合法主机(FQDN, Fully Qualified Domain Name)发送的邮件都会被当做垃圾邮件直接丢弃);

安装sendmail/postfix,配置软件并运行;

配置防火墙、安全组,放行端口。

发送效率低、非面向对象的调用方式,配置麻烦以及云服务器厂商的封锁,是使用mail函数的最大阻碍。所以做PHP以来,本人并未直接用过mail函数。

PHP发送邮件

发个邮件要了解这么多,会让人觉得很心累。说好的PHP是最好的语言呢?

PHP发送邮件也可以很简单,推荐方式就是使用Swift Mailer或PHPMailer等类库。引入这些类库后,注册第三方邮箱(比如Gmail、QQ等),填好用户名密码,配置好STMP地址和端口,就能像发送短信一样轻松发送邮件。当然这些类库也支持使用sendmail/postfix发送邮件,但我想你不会再这样做了。

以Swift Mailer为例,直接上代码说明使用PHP发送邮件也是一个非常简单的事情!

首先,在项目中引入Swift Mailer:

composer require "swiftmailer/swiftmailer:^6.0"

然后准备好邮件内容(以文本文件为例,不带附件):

$message = (new Swift_Message('Test Message'))
  ->setFrom(['tlanyan@tlanyan.me' => 'tlanyan'])
  ->setTo(['tlanyan1@tlanyan.me'])
  ->setBody('Hello, this is a test mail from Swift Mailer!');

接着,设置好邮件传输方式(使用Gmail邮箱):

$transport = (new Swift_SmtpTransport('smtp.gmail.com', 465, 'ssl'))
  ->setUsername('username')
  ->setPassword('password');

或者使用sendmail/postfix的方式(不推荐):

$transport = (new Swift_SendmailTransport());

最后,使用transport构造mailer实例,发送邮件:

$mailer = new Swift_Mailer($transport);
$result = $mailer->send($message);

老板再也不用担心发送邮件收不到了,So easy!

总结

本文先回顾了发送邮件的相关概念,说明不推荐使用内置的mail函数原因,最后给出了使用第三方类库发送邮件的代码示例。

PHP 相关文章推荐
汉字转化为拼音(php版)
Oct 09 PHP
MYSQL数据库初学者使用指南
Nov 16 PHP
详解:――如何将图片储存在数据库里
Dec 05 PHP
php Undefined index和Undefined variable的解决方法
Mar 27 PHP
php eval函数用法 PHP中eval()函数小技巧
Oct 31 PHP
淘宝ip地址查询类分享(利用淘宝ip库)
Jan 07 PHP
php去除数组中重复数据
Nov 18 PHP
PHP批量生成图片缩略图的方法
Jun 18 PHP
yii权限控制的方法(三种方法)
Dec 28 PHP
php通过执行CutyCapt命令实现网页截图的方法
Sep 30 PHP
Thinkphp框架中D方法与M方法的区别
Dec 23 PHP
php简单生成一组与多组随机字符串的方法
May 09 PHP
PHP学习笔记之session
May 06 #PHP
PHP中cookie知识点学习
May 06 #PHP
分析php://output和php://stdout的区别
May 06 #PHP
PHP 布尔值的自增与自减的实现方法
May 03 #PHP
PHPExcel 修改已存在Excel的方法
May 03 #PHP
PHP中PDO事务处理操作示例
May 02 #PHP
PHP简单实现解析xml为数组的方法
May 02 #PHP
You might like
百度地图经纬度转换到腾讯地图/Google 对应的经纬度
2015/08/28 PHP
js操作iframe兼容各种主流浏览器示例代码
2013/07/22 Javascript
谷歌地图打不开的解决办法
2014/08/07 Javascript
jquery中attr和prop的区别分析
2015/03/16 Javascript
JavaScript中的substr()方法使用详解
2015/06/06 Javascript
举例讲解JavaScript substring()的使用方法
2015/11/09 Javascript
jquery实现横向图片轮播特效代码分享
2015/11/19 Javascript
JavaScript使用DeviceOne开发实战(二) 生成调试安装包
2015/12/01 Javascript
js文本框输入内容智能提示效果
2015/12/02 Javascript
谈谈我对JavaScript原型和闭包系列理解(随手笔记8)
2015/12/24 Javascript
手机浏览器 后退按钮强制刷新页面方法总结
2016/10/09 Javascript
浅析JS中对函数function的理解(基础篇)
2016/10/14 Javascript
jquery插件开发之选项卡制作详解
2017/08/30 jQuery
Linux Centos7.2下安装nodejs&npm配置全局路径的教程
2018/05/15 NodeJs
webpack项目轻松混用css module的方法
2018/06/12 Javascript
结合axios对项目中的api请求进行封装操作
2020/09/21 Javascript
urllib2自定义opener详解
2014/02/07 Python
Python内置的字符串处理函数详细整理(覆盖日常所用)
2014/08/19 Python
python的exec、eval使用分析
2017/12/11 Python
python实现随机梯度下降(SGD)
2020/03/24 Python
Python加载带有注释的Json文件实例
2018/05/23 Python
Python中浅拷贝copy与深拷贝deepcopy的简单理解
2018/10/26 Python
python 随机生成10位数密码的实现代码
2019/06/27 Python
pytorch 实现cross entropy损失函数计算方式
2020/01/02 Python
Win10里python3创建虚拟环境的步骤
2020/01/31 Python
OpenCV利用python来实现图像的直方图均衡化
2020/10/21 Python
Python中return函数返回值实例用法
2020/11/19 Python
Sneaker Studio捷克:购买运动鞋
2018/07/08 全球购物
法国面料和小百货在线商店:Mondial Tissus
2019/03/23 全球购物
Ever New美国:澳大利亚领先的女装时尚品牌
2019/11/28 全球购物
机械专业应届生求职信
2013/09/21 职场文书
户外婚礼策划方案
2014/02/08 职场文书
大学生通用个人的自我评价
2014/02/10 职场文书
初中高效课堂实施方案
2014/02/26 职场文书
浅谈node.js中间件有哪些类型
2021/04/29 Javascript
详解Redis瘦身指南
2021/05/26 Redis