从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 新闻合订本
    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 新闻合订本
    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

    • 我的 Java 学习路线
    • 安装 Java
    • Java 数据类型

    • Java 多版本配置
    • 面向对象

    • Java 核心类

    • IO

    • Java 与时间

    • 异常处理

    • 哈希和加密算法

    • Java8 新特性

    • 网络编程

      • TCP 编程
      • UDP 编程
      • 发送 Email
        • 什么是 Email
        • 准备 SMTP 登录信息
        • 使用 JavaMail
        • 常见问题
        • 小结
      • 接受邮件
      • HTTP 编程
      • RMI 远程调用
  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • JavaSE
  • 网络编程
2023-04-17
目录

发送 Email

# 发送 Email

我们通常不会直接使用网络层的 TCP 和 UDP 协议,而是使用应用层的协议,例如邮件协议 SMTP,以及 HTTP 协议等等。本文演示下如何使用 SMTP 协议发送邮件。

本文主要参考廖雪峰老师的教程 发送 Email - 廖雪峰的官方网站 (opens new window),并结合自己的实践。

# 什么是 Email

Email 就是电子邮件。电子邮件的应用已经有几十年的历史了,我们熟悉的邮箱地址比如 abc@qq.com,邮件软件比如 Outlook,foxmail 都是用来收发邮件的。

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

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

           ┌──────────┐    ┌──────────┐
           │PostOffice│    │PostOffice│     .───.
┌─────┐    ├──────────┤    ├──────────┤    (   ( )
│═══ ░│───→│ ┌─┐ ┌┐┌┐ │───→│ ┌─┐ ┌┐┌┐ │───→ `─┬─'
└─────┘    │ │░│ └┘└┘ │    │ │░│ └┘└┘ │       │
           └─┴─┴──────┘    └─┴─┴──────┘       │
1
2
3
4
5
6

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

             ┌─────────┐    ┌─────────┐    ┌─────────┐
             │░░░░░░░░░│    │░░░░░░░░░│    │░░░░░░░░░│
┌───────┐    ├─────────┤    ├─────────┤    ├─────────┤    ┌───────┐
│░░░░░░░│    │░░░░░░░░░│    │░░░░░░░░░│    │░░░░░░░░░│    │░░░░░░░│
├───────┤    ├─────────┤    ├─────────┤    ├─────────┤    ├───────┤
│       │───→│O ░░░░░░░│───→│O ░░░░░░░│───→│O ░░░░░░░│←───│       │
└───────┘    └─────────┘    └─────────┘    └─────────┘    └───────┘
   MUA           MTA            MTA            MDA           MUA
1
2
3
4
5
6
7
8

几个术语解释:

  • 我们把类似 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,也可以使用加密端口 465 或 587。
  • SMTP 协议是一个建立在 TCP 之上的协议,任何程序发送邮件都必须遵守 SMTP 协议。使用 Java 程序发送邮件时,我们无需关心 SMTP 协议的底层原理,只需要使用 JavaMail 这个标准 API 就可以直接发送邮件。

# 准备 SMTP 登录信息

假设我们准备使用自己的邮件地址 me@example.com 给小明发送邮件,已知小明的邮件地址是 xm@somewhere.com,发送邮件前,我们首先要确定作为 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 口令。

例如在配置 QQ 邮箱的时候,需要设置收信和发信服务器,并设置账号和密码。

注意 QQ 邮箱使用的是授权码,而不是账号密码。可以通过网页端 mail.qq.com (opens new window) 后,在设置--账户里获取授权码

# 使用 JavaMail

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

# 准备依赖

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

jakarta.mail-2.0.1.jar
jakarta.mail-api-2.1.0.jar
jakarta.activation-2.0.1.jar
1
2
3

这两个包一个是接口定义,一个是具体实现。

如果你会 Maven,可以这样设置 pom.xml

<dependency>
	<groupId>jakarta.mail</groupId>
	<artifactId>jakarta.mail-api</artifactId>
	<version>2.0.1</version>
</dependency>

<dependency>
	<groupId>com.sun.mail</groupId>
	<artifactId>jakarta.mail</artifactId>
	<version>2.0.1</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11

# 配置服务器信息

然后,我们通过 JavaMail API 连接到 SMTP 服务器上。我们创建一个 properties 文件,设置相关参数(密码已脱敏,请自行更换为自己的授权码):

mail.smtp.host=smtp.qq.com
mail.smtp.port=587
mail.smtp.auth=true
mail.smtp.starttls.enable=true
mail.smtp.username=peterjxl@qq.com
mail.smtp.password=****
1
2
3
4
5
6

然后就是加载配置文件,并创建一个 Session 对象:

Properties properties = new Properties();
properties.load(new FileInputStream("src/chapter20/mail.properties"));

String username = properties.getProperty("mail.smtp.username");
String password = properties.getProperty("mail.smtp.password");

Session session = Session.getInstance(properties, new Authenticator() {
    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication(username, password);
    }
});

session.setDebug(true);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

获取 Session 实例时,如果服务器需要认证,还需传入一个 Authenticator 对象,并返回指定的用户名和密码

当我们获取到 Session 实例后,设置调试模式,这样发送邮件时可以看到 SMTP 通信的详细内容,便于调试。

# 发送邮件

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

MimeMessage message = new MimeMessage(session);
//设置发送方地址:
message.setFrom(new InternetAddress("peterjxl@qq.com"));
//设置接收方地址
message.setRecipient(Message.RecipientType.TO, new InternetAddress("peterjxl@qq.com"));
//设置邮件主题
message.setSubject("Hello", "UTF-8");
//设置邮件正文
message.setText("Hi PeterJXL", "UTF-8");
Transport.send(message);
1
2
3
4
5
6
7
8
9
10

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

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

DEBUG: setDebug: Jakarta Mail version 2.0.1
DEBUG: getProvider() returning jakarta.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.qq.com, user=peterjxl, password=<null>
DEBUG SMTP: useEhlo true, useAuth true
DEBUG SMTP: trying to connect to host "smtp.qq.com", port 587, isSSL false
220 newxmesmtplogicsvrszb9-0.qq.com XMail Esmtp QQ Mail Server.
DEBUG SMTP: connected to host "smtp.qq.com", port: 587
EHLO peter
250-newxmesmtplogicsvrszb9-0.qq.com
250-PIPELINING
250-SIZE 73400320
250-STARTTLS
250-AUTH LOGIN PLAIN XOAUTH XOAUTH2
250-AUTH=LOGIN
250-MAILCOMPRESS
250 8BITMIME
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "SIZE", arg "73400320"
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN XOAUTH XOAUTH2"
DEBUG SMTP: Found extension "AUTH=LOGIN", arg ""
DEBUG SMTP: Found extension "MAILCOMPRESS", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
STARTTLS
220 Ready to start TLS from 116.21.30.25 to newxmesmtplogicsvrszb9-0.qq.com.
EHLO peter
250-newxmesmtplogicsvrszb9-0.qq.com
250-PIPELINING
250-SIZE 73400320
250-AUTH LOGIN PLAIN XOAUTH XOAUTH2
250-AUTH=LOGIN
250-MAILCOMPRESS
250 8BITMIME
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "SIZE", arg "73400320"
DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN XOAUTH XOAUTH2"
DEBUG SMTP: Found extension "AUTH=LOGIN", arg ""
DEBUG SMTP: Found extension "MAILCOMPRESS", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: protocolConnect login, host=smtp.qq.com, user=peterjxl@qq.com, password=<non-null>
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
MAIL FROM:<peterjxl@qq.com>
250 OK
RCPT TO:<peterjxl@qq.com>
250 OK
DEBUG SMTP: Verified Addresses
DEBUG SMTP:   peterjxl@qq.com
DATA
354 End data with <CR><LF>.<CR><LF>.
Date: Sat, 15 Apr 2023 16:12:24 +0800 (CST)
From: peterjxl@qq.com
To: peterjxl@qq.com
Message-ID: <22069592.0.1681546344477@peter>
Subject: Hello
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit

Hi PeterJXL
.
250 OK: queued as.
DEBUG SMTP: message successfully delivered to mail server
QUIT
221 Bye.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

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

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

# 发送 HTML 邮件

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

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

改为:

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

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

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

# 发送附件

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

//准备邮件内容
Multipart multipart = new MimeMultipart();

// 添加text,也就是邮件正文
BodyPart textPart = new MimeBodyPart();
textPart.setContent("<h1>Hello</h1> <p>Hi, PeterJXL</p>", "text/html;charset=utf-8");
multipart.addBodyPart(textPart);

// 添加图片,也就是附件
BodyPart imagePart = new MimeBodyPart();
imagePart.setFileName("fuk.jpg");
InputStream input = new FileInputStream("src/chapter20/fuk.jpg");
imagePart.setDataHandler(new DataHandler(new ByteArrayDataSource(input, "application/octet-stream")));
multipart.addBodyPart(imagePart);

// 设置邮件内容为multipart:
message.setContent(multipart);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

一个 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 中,即可发送。

观察控制台,可以看到一大堆字符串,这是因为 SMTP 是文本协议,二进制数据(图片)需要通过 Base64 编码为纯文本。忘记的同学可以复习下 Base64 编码 | 从 01 开始 (opens new window)

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

# 发送内嵌图片的 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);
1
2
3
4
5
6
7
8
9
10
11
12

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

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

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

# 常见问题

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

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

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

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

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

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

# 小结

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

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

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

上次更新: 2025/6/3 09:31:54
UDP 编程
接受邮件

← UDP 编程 接受邮件→

最近更新
01
新闻合订本 2025-10
10-31
02
2025 年 10 月记
10-30
03
用 AI 批量优化思源笔记排版
10-15
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式