Spring Boot教程(26) – 如何发送邮件 - 知乎
大体上来说,发送的方式有两种,一种是使用SMTP协议,连上SMTP服务器发送邮件,一种是使用第三方的邮件发送服务,调调API就行。我们先来说说前者。
SMTP是“简单邮件传输协议”的简称,是用来发邮件的,与之对应的是用来收邮件的IMAP和POP3协议,后俩协议暂且不提。现在各大邮件服务商比如Gmail,网易邮箱,QQ邮箱等都有提供SMTP服务器方便你发送邮件,比如你本地的邮件客户端,在添加你的邮箱的时候就需要提供SMTP服务器的地址和端口。同时还需要提供的,就是你的邮箱和密码,这个密码通常不是你的登陆密码,而是一个授权码,类似于token的东西,你需要在后台生成,在账户出现安全问题的时候这个授权码是有办法revoke的。有了SMTP服务的地址、端口、邮箱和密码,你就能在邮件客户端上或者编写代码来发送邮件了。
SMTP服务器的地址,不同的服务商会不同,比如网易的就是smtp.163.com
。SMTP协议的端口号是25,使用了TLS加密的端口可能是465、587和994,不同的服务商可能用的还不一样,你需要利用服务商文档和搜索引擎确定哪个是能用的,他们的区别下面会提到。注意!!如果你用的阿里云ECS,用25端口是发不出去邮件的,也就是连不到外面服务器的25端口,应该是出于安全原因或者是防止滥用,总之,记得换端口。
如果你们是一家严肃的企业,考虑到对外形象,邮箱肯定会用到自己的域名。这种情况下,要么是自建邮件服务器,要么是使用市场上的企业邮件服务,比如Gmail和腾讯企业邮箱都可以绑定域名。使用自己的邮箱域名,无非就是更换下SMTP的信息,差别不大。
说完SMTP,再来聊聊第三方的邮件发送服务吧。比较知名的有Mailgun、SendGrid、SendCloud、Amazon SES和阿里云的邮件推送服务等。这些服务的操作大概都是这样的,先把邮箱域名的MX记录解析到他们的服务器(服务商都会说明怎么加记录的),然后使用API或者SMTP来发送,一般用的都是API或者其对应的SDK,方便灵活,SMTP可能更多的是为了兼容老的代码吧,改改配置就行的。发送量小的随便用,量大的话交点钱。
我比较倾向于使用第三方的服务,拿SDK调调API就行,跟使用别的开源库的流程差不多。我的博客(http://fookwood.com)的评论通知服务,用的就是Mailgun,你要是评论了,我就会收到邮件通知,Mailgun额度是每月免费发1w封。
上面我们说了,SMTP的端口是25,协议是明文的,也就说和HTTP协议一样,它容易被黑客嗅探到。大概在20多年前,出了个SMTPS,它利用465端口先建立TLS连接,再发送邮件文本,类似HTTPS,提高安全性。一年后SMTPS和465端口就废弃了(465端口挪作他用),替代它的是STARTTLS协议,利用587端口,客户端连上587端口之后,先获知服务器是否支持加密链接,支持的话就建立TLS连接,然后在其上跑SMTP协议。这个协议“升级”的过程类似于WebSocket。
然后由于历史原因,兼容性原因,465端口一直在很多客户端和服务器上使用。邮件服务提供商也是如此,所以。。。记得看帮助文档,优先用587,再不行用465,是在没辙用25。
既然知道原理了,我们就说说如何用代码通过SMTP发邮件。
JavaMail是一套邮件相关的API,是Jakarta EE的一部分。Spring框架对邮件也进行了个简单的抽象,定义了MailSender
和MailMessage
两个抽象接口,脑补一下大概可以得出他的使用方式应该是mailSender.send(new MailMessage());
这种。这两个接口可以有多种实现,不过目前只有一种,那就是封装了JavaMail的实现,对应的类分别是JavaMailSender
和MimeMailMessage
。
JavaMailSender
有个实现类JavaMailSenderImpl
,new一个他的对象,设置好服务器地址、端口、用户名和密码(授权码),就能发邮件咯。有了Spring Boot之后,创建对象的操作不需要你来搞,你只需要在application.properties
中配置相关信息,Spring Boot会帮你生成好对象并放到容器中。为此你需要先引入Mail Stater:
这样JavaMail和Spring框架的Mail支持,也就是上面提到的那些类都被引入到了项目中。然后配置下,我举两个例子,分别是Gmail和网易邮箱的配置:
Gmail配置
网易邮箱配置
上面的两张图中,分别使用了587端口和465端口,也就是分别使用了STARTTLS和TLS/SSL。以spring.mail.properties
开头的属性都传递给了JavaMail,Gmail的配置中开启了STARTTLS的支持,并且强制要求使用,如果服务器不支持STARTTLS,将会发生异常。网易的配置中开启了TLS的支持,也就是传输邮件需要建立在加密连接上。
我在看Spring源码的时候发现,如果需要使用用户名密码验证的话,需要将mail.smtp.auth
打开,以便进行身份验证。然而实际上,JavaMail会判断如果用户名和密码都提供了,会自动进行身份验证,上面两个图中注释掉的属性可有可无。相关代码在SMTPTransport
中,有兴趣的可以看看。
属性都配置好了之后,就可以直接在代码中引用JavaMailSender对象了:
如果你的邮件内容足够简单,可以使用SimpleMailMessage
来设置发件人、收件人、抄送人、密送人、标题和内容等等。
有的时候吧,这些个字段不太够。比如说,你设置收件人的昵称,上图中的to
是个邮箱的数组,显然没办法满足需求。你需要更底层的JavaMail中的MimeMessage
:
MimeMessage
对象通过JavaMailSender
来创建,大概是因为MimeMessage
对象操作起来有点复杂,Spring框架提供了MimeMessageHelper
来方便地设置各种内容,包括昵称,附件和HTML文本什么的。看了看源码,这些个类03年04年就加上了,迭代了这么十几年,应该没什么坑了吧。
其实邮件发出去挺简单的,耗时间的应该是邮件模版吧,我还没搞过,听说有兼容性坑,哪天遇到了再总结一下。