阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

发送Email

298次阅读
没有评论

共计 7752 个字符,预计需要花费 20 分钟才能阅读完成。

Email 就是电子邮件。电子邮件的应用已经有几十年的历史了,我们熟悉的邮箱地址比如[email protected],邮件软件比如 Outlook 都是用来收发邮件的。

使用 Java 程序也可以收发电子邮件。我们先来看一下传统的邮件是如何发送的。

传统的邮件是通过邮局投递,然后从一个邮局到另一个邮局,最终到达用户的邮箱:

           ┌──────────┐    ┌──────────┐
           │PostOffice│    │PostOffice│     .───.
┌─────┐    ├──────────┤    ├──────────┤    (()
│═══ ░│───▶│ ┌─┐ ┌┐┌┐ │───▶│ ┌─┐ ┌┐┌┐ │───▶ `─┬─'
└─────┘    │ │░│ └┘└┘ │    │ │░│ └┘└┘ │       │
           └─┴─┴──────┘    └─┴─┴──────┘       │

电子邮件的发送过程也是类似的,只不过是电子邮件是从用户电脑的邮件软件,例如 Outlook,发送到邮件服务器上,可能经过若干个邮件服务器的中转,最终到达对方邮件服务器上,收件方就可以用软件接收邮件:

             ┌─────────┐    ┌─────────┐    ┌─────────┐
             │░░░░░░░░░│    │░░░░░░░░░│    │░░░░░░░░░│
┌───────┐    ├─────────┤    ├─────────┤    ├─────────┤    ┌───────┐
│░░░░░░░│    │░░░░░░░░░│    │░░░░░░░░░│    │░░░░░░░░░│    │░░░░░░░│
├───────┤    ├─────────┤    ├─────────┤    ├─────────┤    ├───────┤
│       │───▶│O ░░░░░░░│───▶│O ░░░░░░░│───▶│O ░░░░░░░│◀───│       │
└───────┘    └─────────┘    └─────────┘    └─────────┘    └───────┘
   MUA           MTA            MTA            MDA           MUA

我们把类似 Outlook 这样的邮件软件称为 MUA:Mail User Agent,意思是给用户服务的邮件代理;邮件服务器则称为 MTA:Mail Transfer Agent,意思是邮件中转的代理;最终到达的邮件服务器称为 MDA:Mail Delivery Agent,意思是邮件到达的代理。电子邮件一旦到达 MDA,就不再动了。实际上,电子邮件通常就存储在 MDA 服务器的硬盘上,然后等收件人通过软件或者登陆浏览器查看邮件。

MTA 和 MDA 这样的服务器软件通常是现成的,我们不关心这些服务器内部是如何运行的。要发送邮件,我们关心的是如何编写一个 MUA 的软件,把邮件发送到 MTA 上。

MUA 到 MTA 发送邮件的协议就是 SMTP 协议,它是 Simple Mail Transport Protocol 的缩写,使用标准端口 25,也可以使用加密端口465587

SMTP 协议是一个建立在 TCP 之上的协议,任何程序发送邮件都必须遵守 SMTP 协议。使用 Java 程序发送邮件时,我们无需关心 SMTP 协议的底层原理,只需要使用 JavaMail 这个标准 API 就可以直接发送邮件。

准备 SMTP 登录信息

假设我们准备使用自己的邮件地址 [email protected] 给小明发送邮件,已知小明的邮件地址是[email protected],发送邮件前,我们首先要确定作为 MTA 的邮件服务器地址和端口号。邮件服务器地址通常是smtp.example.com,端口号由邮件服务商确定使用 25、465 还是 587。以下是一些常用邮件服务商的 SMTP 信息:

  • QQ 邮箱:SMTP 服务器是smtp.qq.com,端口是 465/587;
  • 163 邮箱:SMTP 服务器是smtp.163.com,端口是 465;
  • Gmail 邮箱:SMTP 服务器是smtp.gmail.com,端口是 465/587。

有了 SMTP 服务器的域名和端口号,我们还需要 SMTP 服务器的登录信息,通常是使用自己的邮件地址作为用户名,登录口令是用户口令或者一个独立设置的 SMTP 口令。

我们来看看如何使用 JavaMail 发送邮件。

首先,我们需要创建一个 Maven 工程,并把 JavaMail 相关的两个依赖加入进来:

  • jakarta.mail:javax.mail-api:2.0.1
  • com.sun.mail:jakarta.mail:2.0.1

这两个包一个是接口定义,一个是具体实现。如果使用早期的 1.x 版本,则需注意引入的包名有所不同:

  • javax.mail:javax.mail-api:1.6.2
  • com.sun.mail:javax.mail:1.6.2

并且代码引用的 jakarta.mail 需替换为javax.mail

然后,我们通过 JavaMail API 连接到 SMTP 服务器上:

// 服务器地址:
String smtp = "smtp.office365.com";
// 登录用户名:
String username = "[email protected]";
// 登录口令:
String password = "********";
// 连接到 SMTP 服务器 587 端口:
Properties props = new Properties();
props.put("mail.smtp.host", smtp); // SMTP 主机名
props.put("mail.smtp.port", "587"); // 主机端口号
props.put("mail.smtp.auth", "true"); // 是否需要用户认证
props.put("mail.smtp.starttls.enable", "true"); // 启用 TLS 加密
// 获取 Session 实例:
Session session = Session.getInstance(props, new Authenticator() {protected PasswordAuthentication getPasswordAuthentication() {return new PasswordAuthentication(username, password);
    }
});
// 设置 debug 模式便于调试:
session.setDebug(true);

以 587 端口为例,连接 SMTP 服务器时,需要准备一个 Properties 对象,填入相关信息。最后获取 Session 实例时,如果服务器需要认证,还需要传入一个 Authenticator 对象,并返回指定的用户名和口令。

当我们获取到 Session 实例后,打开调试模式可以看到 SMTP 通信的详细内容,便于调试。

发送邮件

发送邮件时,我们需要构造一个 Message 对象,然后调用 Transport.send(Message) 即可完成发送:

MimeMessage message = new MimeMessage(session);
// 设置发送方地址:
message.setFrom(new InternetAddress("[email protected]"));
// 设置接收方地址:
message.setRecipient(Message.RecipientType.TO, new InternetAddress("[email protected]"));
// 设置邮件主题:
message.setSubject("Hello", "UTF-8");
// 设置邮件正文:
message.setText("Hi Xiaoming...", "UTF-8");
// 发送:
Transport.send(message);

绝大多数邮件服务器要求发送方地址和登录用户名必须一致,否则发送将失败。

填入真实的地址,运行上述代码,我们可以在控制台看到 JavaMail 打印的调试信息:

这是 JavaMail 打印的调试信息:
DEBUG: setDebug: JavaMail version 1.6.2
DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]
DEBUG SMTP: need username and password for authentication
DEBUG SMTP: protocolConnect returning false, host=smtp.office365.com, ...
DEBUG SMTP: useEhlo true, useAuth true
开始尝试连接 smtp.office365.com:
DEBUG SMTP: trying to connect to host "smtp.office365.com", port 587, ...
DEBUG SMTP: connected to host "smtp.office365.com", port: 587
发送命令 EHLO:
EHLO localhost
SMTP 服务器响应 250:
250-SG3P274CA0024.outlook.office365.com Hello
250-SIZE 157286400
...
DEBUG SMTP: Found extension "SIZE", arg "157286400"
发送命令 STARTTLS:
STARTTLS
SMTP 服务器响应 220:
220 2.0.0 SMTP server ready
EHLO localhost
250-SG3P274CA0024.outlook.office365.com Hello [111.196.164.63]
250-SIZE 157286400
250-PIPELINING
250-...
DEBUG SMTP: Found extension "SIZE", arg "157286400"
...
尝试登录:
DEBUG SMTP: protocolConnect login, host=smtp.office365.com, user=********, password=********
DEBUG SMTP: Attempt to authenticate using mechanisms: LOGIN PLAIN DIGEST-MD5 NTLM XOAUTH2 
DEBUG SMTP: Using mechanism LOGIN
DEBUG SMTP: AUTH LOGIN command trace suppressed
登录成功:
DEBUG SMTP: AUTH LOGIN succeeded
DEBUG SMTP: use8bit false
开发发送邮件,设置 FROM:
MAIL FROM:<********@outlook.com>
250 2.1.0 Sender OK
设置 TO:
RCPT TO:<********@sina.com>
250 2.1.5 Recipient OK
发送邮件数据:
DATA
服务器响应 354:
354 Start mail input; end with <CRLF>.<CRLF>
真正的邮件数据:
Date: Mon, 2 Dec 2019 09:37:52 +0800 (CST)
From: ********@outlook.com
To: ********[email protected]
Message-ID: <1617791695.0.1575250672483@localhost>
邮件主题是编码后的文本:
Subject: =?UTF-8?Q?JavaMail=E9=82=AE=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64

邮件正文是 Base64 编码的文本:
SGVsbG8sIOi/meaYr+S4gOWwgeadpeiHqmphdmFtYWls55qE6YKu5Lu277yB
.
邮件数据发送完成后,以 \r\n.\r\n 结束,服务器响应 250 表示发送成功:
250 2.0.0 OK <HK0PR03MB4961.apcprd03.prod.outlook.com> [Hostname=HK0PR03MB4961.apcprd03.prod.outlook.com]
DEBUG SMTP: message successfully delivered to mail server
发送 QUIT 命令:
QUIT
服务器响应 221 结束 TCP 连接:
221 2.0.0 Service closing transmission channel

从上面的调试信息可以看出,SMTP 协议是一个请求 - 响应协议,客户端总是发送命令,然后等待服务器响应。服务器响应总是以数字开头,后面的信息才是用于调试的文本。这些响应码已经被定义在 SMTP 协议中了,查看具体的响应码就可以知道出错原因。

如果一切顺利,对方将收到一封文本格式的电子邮件:

发送 Email

发送 HTML 邮件

发送 HTML 邮件和文本邮件是类似的,只需要把:

message.setText(body, "UTF-8");

改为:

message.setText(body, "UTF-8", "html");

传入的 body 是类似 <h1>Hello</h1><p>Hi, xxx</p> 这样的 HTML 字符串即可。

HTML 邮件可以在邮件客户端直接显示为网页格式:

发送 Email

发送附件

要在电子邮件中携带附件,我们就不能直接调用 message.setText() 方法,而是要构造一个 Multipart 对象:

Multipart multipart = new MimeMultipart();
// 添加 text:
BodyPart textpart = new MimeBodyPart();
textpart.setContent(body, "text/html;charset=utf-8");
multipart.addBodyPart(textpart);
// 添加 image:
BodyPart imagepart = new MimeBodyPart();
imagepart.setFileName(fileName);
imagepart.setDataHandler(new DataHandler(new ByteArrayDataSource(input, "application/octet-stream")));
multipart.addBodyPart(imagepart);
// 设置邮件内容为 multipart:
message.setContent(multipart);

一个 Multipart 对象可以添加若干个 BodyPart,其中第一个BodyPart 是文本,即邮件正文,后面的 BodyPart 是附件。BodyPart依靠 setContent() 决定添加的内容,如果添加文本,用 setContent("...", "text/plain;charset=utf-8") 添加纯文本,或者用 setContent("...", "text/html;charset=utf-8") 添加 HTML 文本。如果添加附件,需要设置文件名(不一定和真实文件名一致),并且添加一个DataHandler(),传入文件的 MIME 类型。二进制文件可以用application/octet-stream,Word 文档则是application/msword

最后,通过 setContent()Multipart添加到 Message 中,即可发送。

带附件的邮件在客户端会被提示下载:

发送 Email

发送内嵌图片的 HTML 邮件

有些童鞋可能注意到,HTML 邮件中可以内嵌图片,这是怎么做到的?

如果给一个<img src="http://example.com/test.jpg">,这样的外部图片链接通常会被邮件客户端过滤,并提示用户显示图片并不安全。只有内嵌的图片才能正常在邮件中显示。

内嵌图片实际上也是一个附件,即邮件本身也是Multipart,但需要做一点额外的处理:

Multipart multipart = new MimeMultipart();
// 添加 text:
BodyPart textpart = new MimeBodyPart();
textpart.setContent("<h1>Hello</h1><p><img src=\"cid:img01\"></p>", "text/html;charset=utf-8");
multipart.addBodyPart(textpart);
// 添加 image:
BodyPart imagepart = new MimeBodyPart();
imagepart.setFileName(fileName);
imagepart.setDataHandler(new DataHandler(new ByteArrayDataSource(input, "image/jpeg")));
// 与 HTML 的 <img src="cid:img01"> 关联:
imagepart.setHeader("Content-ID", "<img01>");
multipart.addBodyPart(imagepart);

在 HTML 邮件中引用图片时,需要设定一个 ID,用类似 <img src=\"cid:img01\"> 引用,然后,在添加图片作为 BodyPart 时,除了要正确设置 MIME 类型(根据图片类型使用 image/jpegimage/png),还需要设置一个 Header:

imagepart.setHeader("Content-ID", "<img01>");

这个 ID 和 HTML 中引用的 ID 对应起来,邮件客户端就可以正常显示内嵌图片:

发送 Email

常见问题

如果用户名或口令错误,会导致 535 登录失败:

DEBUG SMTP: AUTH LOGIN failed
Exception in thread "main" javax.mail.AuthenticationFailedException: 535 5.7.3 Authentication unsuccessful [HK0PR03CA0105.apcprd03.prod.outlook.com]

如果登录用户和发件人不一致,会导致 554 拒绝发送错误:

DEBUG SMTP: MessagingException while sending, THROW: 
com.sun.mail.smtp.SMTPSendFailedException: 554 5.2.0 STOREDRV.Submission.Exception:SendAsDeniedException.MapiExceptionSendAsDenied;

有些时候,如果邮件主题和正文过于简单,会导致 554 被识别为垃圾邮件的错误:

DEBUG SMTP: MessagingException while sending, THROW: 
com.sun.mail.smtp.SMTPSendFailedException: 554 DT:SPM

总之,出错时,需要查看 DEBUG 信息,找到服务器返回的错误码和描述信息来定位错误原因。

练习

使用 SMTP 发送邮件。

下载练习

小结

使用 JavaMail API 发送邮件本质上是一个 MUA 软件通过 SMTP 协议发送邮件至 MTA 服务器;

打开调试模式可以看到详细的 SMTP 交互信息;

某些邮件服务商需要开启 SMTP,并需要独立的 SMTP 登录密码。

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计7752字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中

星哥玩云

星哥玩云
星哥玩云
分享互联网知识
用户数
4
文章数
19351
评论数
4
阅读量
7978896
文章搜索
热门文章
星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛NAS-6:抖音视频同步工具,视频下载自动下载保存

星哥带你玩飞牛 NAS-6:抖音视频同步工具,视频下载自动下载保存 前言 各位玩 NAS 的朋友好,我是星哥!...
星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛NAS-3:安装飞牛NAS后的很有必要的操作

星哥带你玩飞牛 NAS-3:安装飞牛 NAS 后的很有必要的操作 前言 如果你已经有了飞牛 NAS 系统,之前...
我把用了20年的360安全卫士卸载了

我把用了20年的360安全卫士卸载了

我把用了 20 年的 360 安全卫士卸载了 是的,正如标题你看到的。 原因 偷摸安装自家的软件 莫名其妙安装...
再见zabbix!轻量级自建服务器监控神器在Linux 的完整部署指南

再见zabbix!轻量级自建服务器监控神器在Linux 的完整部署指南

再见 zabbix!轻量级自建服务器监控神器在 Linux 的完整部署指南 在日常运维中,服务器监控是绕不开的...
飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛NAS中安装Navidrome音乐文件中文标签乱码问题解决、安装FntermX终端

飞牛 NAS 中安装 Navidrome 音乐文件中文标签乱码问题解决、安装 FntermX 终端 问题背景 ...
阿里云CDN
阿里云CDN-提高用户访问的响应速度和成功率
随机文章
星哥带你玩飞牛 NAS-9:全能网盘搜索工具 13 种云盘一键搞定!

星哥带你玩飞牛 NAS-9:全能网盘搜索工具 13 种云盘一键搞定!

星哥带你玩飞牛 NAS-9:全能网盘搜索工具 13 种云盘一键搞定! 前言 作为 NAS 玩家,你是否总被这些...
在Windows系统中通过VMware安装苹果macOS15

在Windows系统中通过VMware安装苹果macOS15

在 Windows 系统中通过 VMware 安装苹果 macOS15 许多开发者和爱好者希望在 Window...
把小米云笔记搬回家:飞牛 NAS 一键部署,小米云笔记自动同步到本地

把小米云笔记搬回家:飞牛 NAS 一键部署,小米云笔记自动同步到本地

把小米云笔记搬回家:飞牛 NAS 一键部署,小米云笔记自动同步到本地 大家好,我是星哥,今天教大家在飞牛 NA...
安装Black群晖DSM7.2系统安装教程(在Vmware虚拟机中、实体机均可)!

安装Black群晖DSM7.2系统安装教程(在Vmware虚拟机中、实体机均可)!

安装 Black 群晖 DSM7.2 系统安装教程(在 Vmware 虚拟机中、实体机均可)! 前言 大家好,...
星哥带你玩飞牛NAS-12:开源笔记的进化之路,效率玩家的新选择

星哥带你玩飞牛NAS-12:开源笔记的进化之路,效率玩家的新选择

星哥带你玩飞牛 NAS-12:开源笔记的进化之路,效率玩家的新选择 前言 如何高效管理知识与笔记,已经成为技术...

免费图片视频管理工具让灵感库告别混乱

一言一句话
-「
手气不错
支付宝、淘宝、闲鱼又双叕崩了,Cloudflare也瘫了连监控都挂,根因藏在哪?

支付宝、淘宝、闲鱼又双叕崩了,Cloudflare也瘫了连监控都挂,根因藏在哪?

支付宝、淘宝、闲鱼又双叕崩了,Cloudflare 也瘫了连监控都挂,根因藏在哪? 最近两天的互联网堪称“故障...
自己手撸一个AI智能体—跟创业大佬对话

自己手撸一个AI智能体—跟创业大佬对话

自己手撸一个 AI 智能体 — 跟创业大佬对话 前言 智能体(Agent)已经成为创业者和技术人绕...
零成本上线!用 Hugging Face免费服务器+Docker 快速部署HertzBeat 监控平台

零成本上线!用 Hugging Face免费服务器+Docker 快速部署HertzBeat 监控平台

零成本上线!用 Hugging Face 免费服务器 +Docker 快速部署 HertzBeat 监控平台 ...
开发者福利:免费 .frii.site 子域名,一分钟申请即用

开发者福利:免费 .frii.site 子域名,一分钟申请即用

  开发者福利:免费 .frii.site 子域名,一分钟申请即用 前言 在学习 Web 开发、部署...
恶意团伙利用 PHP-FPM 未授权访问漏洞发起大规模攻击

恶意团伙利用 PHP-FPM 未授权访问漏洞发起大规模攻击

恶意团伙利用 PHP-FPM 未授权访问漏洞发起大规模攻击 PHP-FPM(FastCGl Process M...