详解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学习资源和链接.
Dec 05 PHP
如何突破PHP程序员的技术瓶颈分析
Jul 17 PHP
提示Trying to clone an uncloneable object of class Imagic的解决
Oct 27 PHP
基于php iconv函数的使用详解
Jun 09 PHP
解决FastCGI 进程超过了配置的活动超时时限的问题
Jul 03 PHP
PHP反射类ReflectionClass和ReflectionObject的使用方法
Nov 13 PHP
php实现的常见排序算法汇总
Sep 08 PHP
php实现XSS安全过滤的方法
Jul 29 PHP
php实现购物车功能(上)
Jul 23 PHP
PHP开发制作一个简单的活动日程表Calendar
Jun 20 PHP
Laravel 5.4因特殊字段太长导致migrations报错的解决
Oct 22 PHP
在Laravel的Model层做数据缓存的实现
Sep 26 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
PHP print类函数使用总结
2010/06/25 PHP
PHP加Nginx实现动态裁剪图片方案
2014/03/10 PHP
PHP如何使用Memcached
2016/04/05 PHP
TP5(thinkPHP5框架)实现显示错误信息及行号功能的方法
2019/06/03 PHP
Laravel监听数据库访问,打印SQL的例子
2019/10/24 PHP
PHP安全之register_globals的on和off的区别
2020/07/23 PHP
Auntion-TableSort国人写的一个javascript表格排序的东西
2007/11/12 Javascript
js 替换
2008/02/19 Javascript
悄悄用脚本检查你访问过哪些网站的代码
2010/12/04 Javascript
jQuery实现冻结表格行和列
2015/04/29 Javascript
基于javascript实现根据身份证号码识别性别和年龄
2016/01/22 Javascript
快速获取/设置iframe内对象元素的几种js实现方法
2016/05/20 Javascript
javascript 广告移动特效的实现代码
2016/06/25 Javascript
js仿百度切换皮肤功能(html+css)
2016/07/10 Javascript
BootStrap下拉框在firefox浏览器界面不友好的解决方案
2016/08/18 Javascript
node学习记录之搭建web服务器教程
2017/02/16 Javascript
Js面试算法详解
2018/04/08 Javascript
JS无限级导航菜单实现方法
2019/01/05 Javascript
jQuery实现checkbox全选、反选及删除等操作的方法详解
2019/08/02 jQuery
Vue跨域请求问题解决方案过程解析
2020/08/07 Javascript
Python的collections模块中的OrderedDict有序字典
2016/07/07 Python
Python3 循环语句(for、while、break、range等)
2017/11/20 Python
python+splinter实现12306网站刷票并自动购票流程
2018/09/25 Python
Python第三方库安装缓慢的解决方法
2021/02/06 Python
Born鞋子官网:Born Shoes
2017/04/06 全球购物
诉讼财产保全担保书
2014/05/20 职场文书
超市理货员岗位职责
2014/07/04 职场文书
党员民主生活会对照检查材料思想汇报
2014/09/28 职场文书
安全员岗位职责范本
2015/04/11 职场文书
周恩来的四个昼夜观后感
2015/06/03 职场文书
2015年新教师个人工作总结
2015/10/14 职场文书
2016清明节森林防火广播稿
2015/12/17 职场文书
JS异步堆栈追踪之为什么await胜过Promise
2021/04/28 Javascript
关于mysql中时间日期类型和字符串类型的选择
2021/11/27 MySQL
Python利用capstone实现反汇编
2022/04/06 Python
CSS list-style-type属性使用方法
2023/05/21 HTML / CSS