详解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下清空字符串中的HTML标签的代码
Sep 06 PHP
php下安装配置fckeditor编辑器的方法
Mar 02 PHP
ThinkPHP 3.2 版本升级了哪些内容
Mar 05 PHP
Symfony2安装第三方Bundles实例详解
Feb 04 PHP
php解析mht文件转换成html的实例
Mar 13 PHP
总结一些PHP中好用但又容易忽略的小知识
Jun 02 PHP
详解php实现页面静态化原理
Jun 21 PHP
PHP数组内存利用率低和弱类型详细解读
Aug 10 PHP
PHP递归实现文件夹的复制、删除、查看大小操作示例
Aug 11 PHP
如何通过View::first使用Laravel Blade的动态模板详解
Sep 21 PHP
php支付宝系列之电脑网站支付
May 30 PHP
Laravel框架实现多个视图共享相同数据的方法详解
Jul 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
PHP实现即时输出、实时输出内容方法
2015/05/27 PHP
Thinkphp微信公众号支付接口
2016/08/04 PHP
javascript document.execCommand() 常用解析
2009/12/14 Javascript
javascript的offset、client、scroll使用方法详解
2012/12/25 Javascript
js弹出div并显示遮罩层
2014/02/12 Javascript
js分页代码分享
2014/04/28 Javascript
JavaScript常用的返回,自动跳转,刷新,关闭语句汇总
2015/01/13 Javascript
angularJS 中$attrs方法使用指南
2015/02/09 Javascript
jquery validate和jquery form 插件组合实现验证表单后AJAX提交
2015/08/26 Javascript
完美实现js焦点轮播效果(二)(图片可滚动)
2017/03/07 Javascript
Vue学习笔记进阶篇之多元素及多组件过渡
2017/07/19 Javascript
使用mint-ui开发项目的一些心得(分享)
2017/09/07 Javascript
Angular 作用域scope的具体使用
2017/12/11 Javascript
利用jQuery+localStorage实现一个简易的计时器示例代码
2017/12/25 jQuery
vue动态设置img的src路径实例
2018/09/18 Javascript
JS块级作用域和私有变量实例分析
2019/05/11 Javascript
jquery使用echarts实现有向图可视化功能示例
2019/11/25 jQuery
Javascript基于OOP实实现探测器功能代码实例
2020/08/26 Javascript
[01:39:42]Fnatic vs Mineski 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/18 DOTA
Python爬虫模拟登录带验证码网站
2016/01/22 Python
wxpython中Textctrl回车事件无效的解决方法
2016/07/21 Python
浅析PyTorch中nn.Module的使用
2019/08/18 Python
使用pytorch完成kaggle猫狗图像识别方式
2020/01/10 Python
python框架Django实战商城项目之工程搭建过程图文详解
2020/03/09 Python
基于keras中的回调函数用法说明
2020/06/17 Python
CSS3实现div从下往上滑入滑出效果示例
2020/04/28 HTML / CSS
美国在线鲜花速递:ProFlowers
2017/01/05 全球购物
美国家庭鞋店:Shoe Sensation
2019/09/27 全球购物
如何查找和删除数据库中的重复数据
2014/11/05 面试题
中学生英语演讲稿
2014/04/26 职场文书
反对形式主义、官僚主义、享乐主义和奢靡之风整改措施
2014/09/17 职场文书
幼儿园个人师德总结
2015/02/06 职场文书
2015年基层党建工作总结
2015/05/14 职场文书
基于python制作简易版学生信息管理系统
2021/04/20 Python
Oracle创建只读账号的详细步骤
2021/06/07 Oracle
一次项目中Thinkphp绕过禁用函数的实战记录
2021/11/17 PHP