从01开始 从01开始
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)

peterjxl

人生如逆旅,我亦是行人
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)
  • JavaSE

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

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

    • Java核心类

    • IO

    • Java与时间

    • 异常处理

    • 哈希和加密算法

    • Java8新特性

    • 网络编程

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

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java并发

  • Java源码

  • JVM

  • 韩顺平

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

发送Email

# 30.发送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登录密码。

‍

在GitHub上编辑此页 (opens new window)
上次更新: 2023/4/18 09:35:05
UDP编程
接受邮件

← UDP编程 接受邮件→

Theme by Vdoing | Copyright © 2022-2023 粤ICP备2022067627号-1 粤公网安备 44011302003646号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式