文章目录
Email就是电子邮件。电子邮件的应用已经有几十年的历史了,我们熟悉的邮箱地址比如abc@example.com,邮件软件比如
Foxmail
都是用来收发邮件的。
发送邮件
传统的邮件是如何发送的
传统的邮件是通过邮局投递,然后从一个邮局到另一个邮局,最终到达用户的邮箱:
电子邮件是如何发送的
电子邮件的发送过程也是类似的,只不过是电子邮件是从用户电脑的邮件软件,例如FoxMail,发送到邮件服务器上,可能经过若干个邮件服务器的中转,最终到达对方邮件服务器上,收件方就可以用软件接收邮件
MUA/MTA/MDT和SMTP协议
-
我们把类似FoxMail这样的
邮件软件
称为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就可以直接发送邮件。
Java中使用JavaEmail发送邮件
准备SMTP登录信息
- 发送邮件前,我们首先要确定作为
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。
- QQ邮箱:SMTP服务器是
- 有了SMTP服务器的域名和端口号,我们还需要SMTP服务器的登录信息,通常是使用
自己的邮件地址作为用户名
,登录口令
是用户口令
或者一个独立设置的SMTP口令
。
假设我们准备使用自己的邮件地址me@example.com
给小明
发送邮件,已知小明的邮件地址是qq877728715@163.com
- 使用maven依赖
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
- 配置SMTP登录信息
// 服务器地址:
String smtp = "smtp.163.com";
// 登录用户名:
String username = "m17603050524@163.com";
// 登录口令:
String password = "qq775825800";
//主机端口号
String port = "465";
// 连接到SMTP服务器587端口:
Properties props = new Properties();
props.put("mail.smtp.host", smtp); // SMTP主机名
props.put("mail.smtp.port", port); // 主机端口号
props.put("mail.smtp.auth", "true"); // 是否需要用户认证
props.put("mail.smtp.starttls.enable", "true"); // 启用TLS加密
//解决因为163邮箱非开发端口465报错
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
// 获取Session实例:
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
// 设置debug模式便于调试:
session.setDebug(true);
-
以465端口为例,连接SMTP服务器时,需要
准备一个Properties对象,填入相关信息
。最后获取Session实例时, 如果服务器需要认证,还需要传入一个Authenticator对象
,并返回指定的用户名和口令
。 -
当我们获取到Session实例后,打开调试模式可以看到SMTP通信的详细内容,便于调试。
发送邮件
- 发送邮件时,我们需要构造一个
Message对象
,然后调用Transport.send(Message)即可完成发送:
//---------------设置发送邮件正文-----------------------
MimeMessage message = new MimeMessage(session);
// 设置发送方地址:
message.setFrom(new InternetAddress("m17603050524@163.com"));
// 设置接收方地址:
message.setRecipient(Message.RecipientType.TO, new InternetAddress("qq877728715@163.com"));
// 设置邮件主题:
message.setSubject("Hello", "UTF-8");
// 设置邮件正文:
message.setText("Hi qq877728715...", "UTF-8");
// 发送:
Transport.send(message);
绝大多数邮件服务器要求发送方地址和登录用户名必须一致,否则发送将失败
- 填入真实的地址,运行上述代码,我们可以在控制台看到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.163.com, user=87772, password=<null>
DEBUG SMTP: useEhlo true, useAuth true
#开始尝试连接smtp.163.com
DEBUG SMTP: trying to connect to host "smtp.163.com", port 465, isSSL false
220 163.com Anti-spam GT for Coremail System (163com[20141201])
DEBUG SMTP: connected to host "smtp.163.com", port: 465
#发送命令EHLO:
EHLO DESKTOP-GE36VVD
#SMTP服务器响应250:
250-mail
250-PIPELINING
250-AUTH LOGIN PLAIN
250-AUTH=LOGIN PLAIN
250-coremail 1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2Ur3wGB5UCa0xDrUUUUj
#发送命令STARTTLS
250-STARTTLS
250 8BITMIME
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN"
DEBUG SMTP: Found extension "AUTH=LOGIN", arg "PLAIN"
DEBUG SMTP: Found extension "coremail", arg "1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2Ur3wGB5UCa0xDrUUUUj"
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: STARTTLS requested but already using SSL
#尝试登录
DEBUG SMTP: protocolConnect login, host=smtp.163.com, user=m17603050524@163.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
#开发发送邮件,设置FROM
MAIL FROM:<m17603050524@163.com>
250 Mail OK
#设置TO
RCPT TO:<qq877728715@163.com>
250 Mail OK
DEBUG SMTP: Verified Addresses
DEBUG SMTP: qq877728715@163.com
#发送邮件数据
DATA
#服务器响应354
354 End data with <CR><LF>.<CR><LF>
#真正的邮件数据
Date: Thu, 30 Jan 2020 14:01:07 +0800 (CST)
From: m17603050524@163.com
To: qq877728715@163.com
Message-ID: <2114664380.0.1580364067964@DESKTOP-GE36VVD>
#邮件主题
Subject: Hello
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit
#邮件正文
Hi qq877728715...
.
#邮件数据发送完成后,以\r\n.\r\n结束,服务器响应250表示发送成功:
250 Mail OK queued as smtp7,C8CowABnZwMocTJesciMIw--.9151S2 1580364073
DEBUG SMTP: message successfully delivered to mail server
#发送QUIT命令
QUIT
221 Bye
从上面的调试信息可以看出,SMTP协议是一个请求-响应协议,客户端总是发送命令,然后等待服务器响应。服务器响应总是以数字开头,后面的信息才是用于调试的文本。 这些响应码已经被定义在SMTP协议中了,查看具体的响应码就可以知道出错原因。
如果一切顺利,对方将收到一封文本格式的电子邮件:
发送HTML邮件
发送HTML邮件和文本邮件是类似的,只需要把:
message.setText(body, "UTF-8");
改为:
message.setText(body, "UTF-8", "html");
- 传入的body是类似
<h1>Hello</h1><p>Hi, xxx</p>
这样的HTML字符串即可。 - HTML邮件可以在邮件客户端直接显示为网页格式:
发送附件
要在电子邮件中携带附件,我们就不能直接调用message.setText()方法,而是要构造一个Multipart对象
:
Multipart multipart = new MimeMultipart();
// 添加text:第一个BodyPart是文本,即邮件正文,后面的BodyPart是附件
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(inputStream, "application/octet-stream")));
multipart.addBodyPart(imagepart);
// 设置邮件内容为multipart:
message.setContent(multipart);
-
一个
Multipart对象
可以添加若干个BodyPart
,其中第一个BodyPart是文本,即邮件正文,后面的BodyPart是附件。 -
BodyPart
依靠setContent()决定添加的内容
,- 添加
文本
,用setContent("…", “text/plain;charset=utf-8”)添加纯文本, - 添加
HTML文本
用setContent("…", “text/html;charset=utf-8”)。 - 如
添加附件
,需要设置文件名
(不一定和真实文件名一致),并且添加一个DataHandler()
,传入文件的MIME类型
。二进制文件可以用application/octet-stream
,Word文档则是application/msword
。
- 添加
-
最后,通过setContent()把Multipart添加到
Message
中,即可发送。
带附件的邮件在客户端会被提示下载:
发送内嵌图片的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/jpeg或image/png),还需要设置一个Header:
imagepart.setHeader("Content-ID", "<img01>");
这个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]
- 如果登录用户和发件人不一致,会导致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
- 发送163邮件时报错:
Could not connect to SMTP host: smtp.163.com, port: 465, response: -1
- 方法一:加多以下这句
props.put("mail.smtp.ssl.enable", true);
- 方法二:修改发送端口
将465端口改为25端口
-
http://blog.sina.com.cn/s/blog_5ceb51480102x1no.html
https://blog.csdn.net/cry1049208942/article/details/97183552
https://blog.csdn.net/hao134838/article/details/85761003
- 方法一:加多以下这句
发送邮件完整代码
import javax.activation.DataHandler;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/**
* @Description TODO 发送邮件
* @Author JianPeng OuYang
* @Date 2020/1/30 13:45
* @Version v1.0
*/
public class SendEmail {
public static void main(String[] args) throws MessagingException, IOException {
// 服务器地址:
String smtp = "smtp.163.com";
// 登录用户名:
String username = "m17603050524@163.com";
// 登录口令:
String password = "***********";
//主机端口号
String port = "25";//25为开放端口,465为非开放端口
// 连接到SMTP服务器587端口:
Properties props = new Properties();
props.put("mail.smtp.host", smtp); // SMTP主机名
props.put("mail.smtp.port", port); // 主机端口号
props.put("mail.smtp.auth", "true"); // 是否需要用户认证
props.put("mail.smtp.starttls.enable", "true"); // 启用TLS加密
//解决因为163邮箱非开发端口465报错
//props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
// 获取Session实例:
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
// 设置debug模式便于调试:
session.setDebug(true);
//---------------设置发送邮件正文-----------------------
MimeMessage message = new MimeMessage(session);
// 设置发送方地址:
message.setFrom(new InternetAddress("m17603050524@163.com"));
// 设置接收方地址:
message.setRecipient(Message.RecipientType.TO, new InternetAddress("qq877728715@163.com"));
// 设置邮件主题:
message.setSubject("你好兄弟,吃饭了吗?", "UTF-8");
// 设置邮件正文:
// -------------------1.发送文本
// message.setText("从上面的调试信息可以看出,**SMTP协议是一个请求-响应协议,客户端总是发送命令,然后等待服务器响应。服务器响应总是以数字开头,后面的信息才是用于调试的文本。** `这些响应码已经被定义在SMTP协议中了,查看具体的响应码就可以知道出错原因。`如果一切顺利,对方将收到一封文本格式的电子邮件:", "UTF-8");
// -------------------2.发送html文本
// message.setText("<ol><li><h1><strong>星期一</strong><br></h1></li><li><h1><strong>星期二</strong></h1></li><li><h1><strong>星期三</strong></h1></li></ol>", "UTF-8", "html");
// -------------------3.发送html文本+附件
Multipart multipart = new MimeMultipart();
//---------添加text:第一个BodyPart是文本,即邮件正文,后面的BodyPart是附件
BodyPart textpart = new MimeBodyPart();
textpart.setContent("<h1>Hello</h1><p><img src=\"cid:img01\"></p>", "text/html;charset=utf-8");
//添加附件到multipart中
multipart.addBodyPart(textpart);
//---------添加image用于在正文中引用当前图片
BodyPart imagepart = new MimeBodyPart();
imagepart.setFileName("a.jpg");
imagepart.setDataHandler(new DataHandler(new ByteArrayDataSource(new FileInputStream(new File("a.jpg")), "image/jpeg")));
//----------与HTML的<img src="cid:img01">关联:
imagepart.setHeader("Content-ID", "<img01>");
//添加附件到multipart中
multipart.addBodyPart(imagepart);
// 添加附件1:
BodyPart filePart = new MimeBodyPart();
filePart.setFileName("aaaRandom.txt");//设置文件名
//把数据源添加到附件中
filePart.setDataHandler(new DataHandler(new ByteArrayDataSource(new FileInputStream(new File("aaaRandom.txt")), "application/octet-stream")));
//-----------添加指定数据到文本文件中
//txtPart.setContent("添加指定数据到文本文件中","text/html;charset=UTF-8");
//添加附件到multipart中
multipart.addBodyPart(filePart);
// 设置邮件内容为multipart:
message.setContent(multipart);
// 发送:
Transport.send(message);
}
}
效果:发送txt附件且在正文中引用图片
小结
- 使用JavaMail API发送邮件本质上是一个MUA软件通过SMTP协议发送邮件至MTA服务器;
- 打开调试模式可以看到详细的SMTP交互信息;
- 某些邮件服务商需要开启SMTP,并需要独立的SMTP登录密码。
接收邮件
发送邮件在上面已经讲过了,客户端总是通过SMTP协议
把邮件发送给MTA(邮件中转的代理服务器
)
-
接收Email则相反,因为邮件最终到达收件人的
MDA
服务器(邮件总代理服务器
),所以接收邮件是收件人用自己的客户端
把邮件从MDA服务器
上抓取到本地
的过程。 -
接收邮件使用最广泛的协议是
POP3
:Post Office Protocol version 3 ,它也是一个建立在TCP连接之上的协议。POP3服务器的标准端口是110
,如果整个会话需要加密,那么使用加密端口995
。 -
另一种接收邮件的协议是
IMAP
:Internet Mail Access Protocol ,它使用标准端口143
和加密端口993
。 -
IMAP和POP3的主要区别是: IMAP协议在
本地
的所有操作都会自动同步到服务器上
,并且,IMAP可以允许用户在邮件服务器的收件箱中创建文件夹
。
JavaMail也提供了IMAP协议的支持
。因为POP3和IMAP的使用方式非常类似
,因此我们只介绍POP3的用法。
Java中使用JavaEmail发送邮件
使用POP3收取Email时,我们无需关心POP3协议底层,因为JavaMail提供了高层接口。首先需要连接到Store对象
// 准备登录信息:
String host = "pop3.example.com";
int port = 995;
String username = "bob@example.com";
String password = "password";
Properties props = new Properties();
props.setProperty("mail.store.protocol", "pop3"); // 协议名称
props.setProperty("mail.pop3.host", host);// POP3主机名
props.setProperty("mail.pop3.port", String.valueOf(port)); // 端口号
// 启动SSL:
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.port", String.valueOf(port));
// 连接到Store:
URLName url = new URLName("pop3", host, post, "", username, password);
Session session = Session.getInstance(props, null);
session.setDebug(true); // 显示调试信息
Store store = new POP3SSLStore(session, url);
store.connect();
一个Store对象
表示整个邮箱的存储,要收取邮件,我们需要通过Store访问指定的Folder(文件夹)
,通常是INBOX
表示收件箱
// 获取收件箱:
Folder folder = store.getFolder("INBOX");
// 以读写方式打开:
folder.open(Folder.READ_WRITE);
// 打印邮件总数/新邮件数量/未读数量/已删除数量:
System.out.println("Total messages: " + folder.getMessageCount());
System.out.println("New messages: " + folder.getNewMessageCount());
System.out.println("Unread messages: " + folder.getUnreadMessageCount());
System.out.println("Deleted messages: " + folder.getDeletedMessageCount());
// 获取每一封邮件:
Message[] messages = folder.getMessages();
for (Message message : messages) {
// 打印每一封邮件:
printMessage((MimeMessage) message);
}
当我们获取到一个Message对象
时,可以强制转型为MimeMessage
,然后打印出邮件主题、发件人、收件人等信息
:
void printMessage(MimeMessage msg) throws IOException, MessagingException {
// 邮件主题:
System.out.println("Subject: " + MimeUtility.decodeText(msg.getSubject()));
// 发件人:
Address[] froms = msg.getFrom();
InternetAddress address = (InternetAddress) froms[0];
String personal = address.getPersonal();
String from = personal == null ? address.getAddress() : (MimeUtility.decodeText(personal) + " <" + address.getAddress() + ">");
System.out.println("From: " + from);
// 继续打印收件人...
}
比较麻烦的是获取邮件的正文。一个MimeMessage
对象也是一个Multipart对象
,它可能只包含一个文本
,也可能是一个Multipart对象
,即由几个Multipart构成
,因此,需要递归
地解析出完整的正文:
String getBody(Part part) throws MessagingException, IOException {
if (part.isMimeType("text/*")) {
// Part是文本:
return part.getContent().toString();
}
if (part.isMimeType("multipart/*")) {
// Part是一个Multipart对象:
Multipart multipart = (Multipart) part.getContent();
// 循环解析每个子Part:
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
String body = getBody(bodyPart);
if (!body.isEmpty()) {
return body;
}
}
}
return "";
}
最后记得关闭Folder和Store
folder.close(true); // 传入true表示删除操作会同步到服务器上(即删除服务器收件箱的邮件)
store.close();
完整代码1
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import java.io.IOException;
import java.util.Properties;
/**
* @Description TODO
* @Author JianPeng OuYang
* @Date 2020/1/30 15:44
* @Version v1.0
*/
public class RecieveEmail {
/**
* 因为现在使用的是163邮箱 而163的 pop地址是 pop3.163.com 端口是 110
* 比如使用好未来企业邮箱 就需要换成 好未来邮箱的 pop服务器地址 pop.263.net 和 端口 110
*/
public static void main(String[] args) throws MessagingException, IOException {
// 准备登录信息:
String host = "pop3.163.com";
int port = 995;
String username = "m17603050524@163.com";
String password = "*****163邮箱授权码*****";
Properties props = new Properties();
props.setProperty("mail.store.protocol", "pop3"); // 协议名称
props.setProperty("mail.pop3.host", host);// POP3主机名
props.setProperty("mail.pop3.port", String.valueOf(port)); // 端口号
// 启动SSL:
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.port", String.valueOf(port));
// 连接到Store:
URLName url = new URLName("pop3", host, port, "", username, password);
Session session = Session.getInstance(props, null);
session.setDebug(true); // 显示调试信息
Store store = new POP3SSLStore(session, url);
store.connect();
// 获取收件箱:
Folder folder = store.getFolder("INBOX");
// 以读写方式打开:
folder.open(Folder.READ_WRITE);
// 打印邮件总数/新邮件数量/未读数量/已删除数量:
System.out.println("邮件总数: " + folder.getMessageCount());
System.out.println("新邮件数量: " + folder.getNewMessageCount());
System.out.println("未读数量: " + folder.getUnreadMessageCount());
System.out.println("已删除数量: " + folder.getDeletedMessageCount());
// 获取每一封邮件:
Message[] messages = folder.getMessages();
for (Message message : messages) {
// 打印每一封邮件:
printMessage((MimeMessage) message);
}
}
static void printMessage(MimeMessage msg) throws IOException, MessagingException {
// 邮件主题:
System.out.println("Subject: " + MimeUtility.decodeText(msg.getSubject()));
// 发件人:
Address[] froms = msg.getFrom();
InternetAddress address = (InternetAddress) froms[0];
String personal = address.getPersonal();
String from = personal == null ? address.getAddress() : (MimeUtility.decodeText(personal) + " <" + address.getAddress() + ">");
System.out.println("From: " + from);
// 继续打印收件人...
}
String getBody(Part part) throws MessagingException, IOException {
if (part.isMimeType("text/*")) {
// Part是文本:
return part.getContent().toString();
}
if (part.isMimeType("multipart/*")) {
// Part是一个Multipart对象:
Multipart multipart = (Multipart) part.getContent();
// 循环解析每个子Part:
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
String body = getBody(bodyPart);
if (!body.isEmpty()) {
return body;
}
}
}
return "";
}
}
执行结果
DEBUG: setDebug: JavaMail version 1.6.2
DEBUG POP3: mail.pop3.rsetbeforequit: false
DEBUG POP3: mail.pop3.disabletop: false
DEBUG POP3: mail.pop3.forgettopheaders: false
DEBUG POP3: mail.pop3.cachewriteto: false
DEBUG POP3: mail.pop3.filecache.enable: false
DEBUG POP3: mail.pop3.keepmessagecontent: false
DEBUG POP3: mail.pop3.starttls.enable: false
DEBUG POP3: mail.pop3.starttls.required: false
DEBUG POP3: mail.pop3.finalizecleanclose: false
DEBUG POP3: mail.pop3.apop.enable: false
DEBUG POP3: mail.pop3.disablecapa: false
DEBUG POP3: connecting to host "pop3.163.com", port 995, isSSL true
+OK Welcome to coremail Mail Pop3 Server (163coms[10774b260cc7a37d26d71b52404dcf5cs])
CAPA
+OK Capability list follows
TOP
USER
PIPELINING
UIDL
LANG
UTF8
SASL PLAIN
STLS
.
DEBUG POP3: PIPELINING enabled
DEBUG POP3: authentication command trace suppressed
DEBUG POP3: authentication command succeeded
STAT
+OK 1 3312
邮件总数: 1
NOOP
+OK core mail
新邮件数量: 0
NOOP
+OK core mail
未读数量: 1
NOOP
+OK core mail
已删除数量: 0
NOOP
+OK core mail
TOP 1 0
+OK 3312 octets
Received: from mail163 (unknown [10.110.6.32])
by trans1 (Coremail) with SMTP id gMCowADXXEK+ojJeDyu4BQ--.34489S2;
Thu, 30 Jan 2020 17:32:46 +0800 (CST)
From: =?UTF-8?B?572R5piT6YKu5Lu25Lit5b+D?= <mail@service.netease.com>
Sender: mail@service.netease.com
To: m17603050524 <m17603050524@163.com>
Message-ID: <1794674068.331356.1580376766821.JavaMail.mail@service.netease.com>
Subject: =?UTF-8?B?6YKu5Lu25bey6KKr5a6i5oi356uv5oiQ5Yqf?=
=?UTF-8?B?5pS25Y+W5bm25Zyo5pyN5Yqh5Zmo5LiK5Yig6Zmk?=
MIME-Version: 1.0
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
X-CM-TRANSID:gMCowADXXEK+ojJeDyu4BQ--.34489S2
X-Coremail-Antispam: 1Uf129KBjDUn29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73
VFW2AGmfu7bjvjm3AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvj4RKdgAUUUUU
X-Originating-IP: [10.110.6.32]
Date: Thu, 30 Jan 2020 17:32:46 +0800 (CST)
.
Subject: 邮件已被客户端成功收取并在服务器上删除
From: 网易邮件中心 <mail@service.netease.com>
完整代码2
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
/**
* 使用POP3协议接收邮件
*/
public class POP3ReceiveMailTest {
public static void main(String[] args) throws Exception {
resceive();
}
/**
* 接收邮件
*/
public static void resceive() throws Exception {
/**
* 因为现在使用的是163邮箱 而163的 pop地址是pop3.163.com 端口是110
* 比如使用好未来企业邮箱 就需要换成 好未来邮箱的 pop服务器地址 pop.263.net 和 端口 110
*/
String userName= "m17603050524@163.com"; // /163邮箱号码
String passWord= "************8"; // 163邮箱授权码
String port = "110"; // 端口号
String servicePath = "pop3.163.com"; // 服务器地址
// 准备连接服务器的会话信息
Properties props = new Properties();
props.setProperty("mail.store.protocol", "pop3"); // 使用pop3协议
props.setProperty("mail.pop3.port", port); // 端口
props.setProperty("mail.pop3.host", servicePath); // pop3服务器
// 创建Session实例对象
Session session = Session.getInstance(props);
Store store = session.getStore("pop3");
store.connect(userName, passWord); //163邮箱程序登录属于第三方登录所以这里的密码是163给的授权密码而并非普通的登录密码
// 获得收件箱
Folder folder = store.getFolder("INBOX");
/* Folder.READ_ONLY:只读权限
* Folder.READ_WRITE:可读可写(可以修改邮件的状态)
*/
folder.open(Folder.READ_WRITE); //打开收件箱
// 由于POP3协议无法获知邮件的状态,所以getUnreadMessageCount得到的是收件箱的邮件总数
System.out.println("未读邮件数: " + folder.getUnreadMessageCount());
// 由于POP3协议无法获知邮件的状态,所以下面得到的结果始终都是为0
System.out.println("删除邮件数: " + folder.getDeletedMessageCount());
System.out.println("新邮件: " + folder.getNewMessageCount());
// 获得收件箱中的邮件总数
System.out.println("邮件总数: " + folder.getMessageCount());
// 得到收件箱中的所有邮件,并解析
Message[] messages = folder.getMessages();
parseMessage(messages);
//得到收件箱中的所有邮件并且删除邮件
//deleteMessage(messages);
//释放资源
folder.close(true);
store.close();
}
/**
* 解析邮件
* @param messages 要解析的邮件列表
*/
public static void parseMessage(Message... messages) throws MessagingException, IOException {
if (messages == null || messages.length < 1) {
throw new MessagingException("未找到要解析的邮件!");
}
// 解析所有邮件
for (int i = 0, count = messages.length; i < count; i++) {
MimeMessage msg = (MimeMessage) messages[i];
System.out.println("------------------解析第" + msg.getMessageNumber() + "封邮件-------------------- ");
System.out.println("主题: " + getSubject(msg));
System.out.println("发件人: " + getFrom(msg));
System.out.println("收件人:" + getReceiveAddress(msg, null));
System.out.println("发送时间:" + getSentDate(msg, null));
System.out.println("是否已读:" + isSeen(msg));
System.out.println("邮件优先级:" + getPriority(msg));
System.out.println("是否需要回执:" + isReplySign(msg));
System.out.println("邮件大小:" + msg.getSize() * 1024 + "kb");
boolean isContainerAttachment = isContainAttachment(msg);
System.out.println("是否包含附件:" + isContainerAttachment);
if (isContainerAttachment) {
saveAttachment(msg, "f:\\mailTest\\" + msg.getSubject() + "_" + i + "_"); //保存附件
}
StringBuffer content = new StringBuffer(30);
getMailTextContent(msg, content);
System.out.println("邮件正文:" + (content.length() > 100 ? content.substring(0, 100) + "..." : content));
System.out.println("------------------第" + msg.getMessageNumber() + "封邮件解析结束-------------------- ");
System.out.println();
}
}
/**
* 解析邮件
* @param messages 要解析的邮件列表
*/
public static void deleteMessage(Message... messages) throws MessagingException, IOException {
if (messages == null || messages.length < 1) {
throw new MessagingException("未找到要解析的邮件!");
}
// 解析所有邮件
for (int i = 0, count = messages.length; i < count; i++) {
/**
* 邮件删除
*/
Message message = messages[i];
String subject = message.getSubject();
// set the DELETE flag to true
message.setFlag(Flags.Flag.DELETED, true);
System.out.println("Marked DELETE for message: " + subject);
}
}
/**
* 获得邮件主题
* @param msg 邮件内容
* @return 解码后的邮件主题
*/
public static String getSubject(MimeMessage msg) throws UnsupportedEncodingException, MessagingException {
return MimeUtility.decodeText(msg.getSubject());
}
/**
* 获得邮件发件人
* @param msg 邮件内容
* @return 姓名 <Email地址>
* @throws MessagingException
* @throws UnsupportedEncodingException
*/
public static String getFrom(MimeMessage msg) throws MessagingException, UnsupportedEncodingException {
String from = "";
Address[] froms = msg.getFrom();
if (froms.length < 1) {
throw new MessagingException("没有发件人!");
}
InternetAddress address = (InternetAddress) froms[0];
String person = address.getPersonal();
if (person != null) {
person = MimeUtility.decodeText(person) + " ";
} else {
person = "";
}
from = person + "<" + address.getAddress() + ">";
return from;
}
/**
* 根据收件人类型,获取邮件收件人、抄送和密送地址。如果收件人类型为空,则获得所有的收件人
* <p>Message.RecipientType.TO 收件人</p>
* <p>Message.RecipientType.CC 抄送</p>
* <p>Message.RecipientType.BCC 密送</p>
* @param msg 邮件内容
* @param type 收件人类型
* @return 收件人1 <邮件地址1>, 收件人2 <邮件地址2>, ...
* @throws MessagingException
*/
public static String getReceiveAddress(MimeMessage msg, Message.RecipientType type) throws MessagingException {
StringBuffer receiveAddress = new StringBuffer();
Address[] addresss = null;
if (type == null) {
addresss = msg.getAllRecipients();
} else {
addresss = msg.getRecipients(type);
}
if (addresss == null || addresss.length < 1) {
throw new MessagingException("没有收件人!");
}
for (Address address : addresss) {
InternetAddress internetAddress = (InternetAddress) address;
receiveAddress.append(internetAddress.toUnicodeString()).append(",");
}
receiveAddress.deleteCharAt(receiveAddress.length() - 1); //删除最后一个逗号
return receiveAddress.toString();
}
/**
* 获得邮件发送时间
* @param msg 邮件内容
* @return yyyy年mm月dd日 星期X HH:mm
* @throws MessagingException
*/
public static String getSentDate(MimeMessage msg, String pattern) throws MessagingException {
Date receivedDate = msg.getSentDate();
if (receivedDate == null) {
return "";
}
if (pattern == null || "".equals(pattern)) {
pattern = "yyyy年MM月dd日 E HH:mm ";
}
return new SimpleDateFormat(pattern).format(receivedDate);
}
/**
* 判断邮件中是否包含附件
* @return 邮件中存在附件返回true,不存在返回false
* @throws MessagingException
* @throws IOException
*/
public static boolean isContainAttachment(Part part) throws MessagingException, IOException {
boolean flag = false;
if (part.isMimeType("multipart/*")) {
MimeMultipart multipart = (MimeMultipart) part.getContent();
int partCount = multipart.getCount();
for (int i = 0; i < partCount; i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
String disp = bodyPart.getDisposition();
if (disp != null && (disp.equalsIgnoreCase(Part.ATTACHMENT) || disp.equalsIgnoreCase(Part.INLINE))) {
flag = true;
} else if (bodyPart.isMimeType("multipart/*")) {
flag = isContainAttachment(bodyPart);
} else {
String contentType = bodyPart.getContentType();
if (contentType.indexOf("application") != -1) {
flag = true;
}
if (contentType.indexOf("name") != -1) {
flag = true;
}
}
if (flag) {
break;
}
}
} else if (part.isMimeType("message/rfc822")) {
flag = isContainAttachment((Part) part.getContent());
}
return flag;
}
/**
* 判断邮件是否已读
* @param msg 邮件内容
* @return 如果邮件已读返回true, 否则返回false
* @throws MessagingException
*/
public static boolean isSeen(MimeMessage msg) throws MessagingException {
return msg.getFlags().contains(Flags.Flag.SEEN);
}
/**
* 判断邮件是否需要阅读回执
* @param msg 邮件内容
* @return 需要回执返回true, 否则返回false
* @throws MessagingException
*/
public static boolean isReplySign(MimeMessage msg) throws MessagingException {
boolean replySign = false;
String[] headers = msg.getHeader("Disposition-Notification-To");
if (headers != null) {
replySign = true;
}
return replySign;
}
/**
* 获得邮件的优先级
* @param msg 邮件内容
* @return 1(High):紧急 3:普通(Normal) 5:低(Low)
* @throws MessagingException
*/
public static String getPriority(MimeMessage msg) throws MessagingException {
String priority = "普通";
String[] headers = msg.getHeader("X-Priority");
if (headers != null) {
String headerPriority = headers[0];
if (headerPriority.indexOf("1") != -1 || headerPriority.indexOf("High") != -1) {
priority = "紧急";
} else if (headerPriority.indexOf("5") != -1 || headerPriority.indexOf("Low") != -1) {
priority = "低";
} else {
priority = "普通";
}
}
return priority;
}
/**
* 获得邮件文本内容
* @param part 邮件体
* @param content 存储邮件文本内容的字符串
* @throws MessagingException
* @throws IOException
*/
public static void getMailTextContent(Part part, StringBuffer content) throws MessagingException, IOException {
//如果是文本类型的附件,通过getContent方法可以取到文本内容,但这不是我们需要的结果,所以在这里要做判断
boolean isContainTextAttach = part.getContentType().indexOf("name") > 0;
if (part.isMimeType("text/*") && !isContainTextAttach) {
content.append(part.getContent().toString());
} else if (part.isMimeType("message/rfc822")) {
getMailTextContent((Part) part.getContent(), content);
} else if (part.isMimeType("multipart/*")) {
Multipart multipart = (Multipart) part.getContent();
int partCount = multipart.getCount();
for (int i = 0; i < partCount; i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
getMailTextContent(bodyPart, content);
}
}
}
/**
* 保存附件
* @param part 邮件中多个组合体中的其中一个组合体
* @param destDir 附件保存目录
* @throws UnsupportedEncodingException
* @throws MessagingException
* @throws FileNotFoundException
* @throws IOException
*/
public static void saveAttachment(Part part, String destDir) throws UnsupportedEncodingException, MessagingException,
FileNotFoundException, IOException {
if (part.isMimeType("multipart/*")) {
Multipart multipart = (Multipart) part.getContent(); //复杂体邮件
//复杂体邮件包含多个邮件体
int partCount = multipart.getCount();
for (int i = 0; i < partCount; i++) {
//获得复杂体邮件中其中一个邮件体
BodyPart bodyPart = multipart.getBodyPart(i);
//某一个邮件体也有可能是由多个邮件体组成的复杂体
String disp = bodyPart.getDisposition();
if (disp != null && (disp.equalsIgnoreCase(Part.ATTACHMENT) || disp.equalsIgnoreCase(Part.INLINE))) {
InputStream is = bodyPart.getInputStream();
saveFile(is, destDir, decodeText(bodyPart.getFileName()));
} else if (bodyPart.isMimeType("multipart/*")) {
saveAttachment(bodyPart, destDir);
} else {
String contentType = bodyPart.getContentType();
if (contentType.indexOf("name") != -1 || contentType.indexOf("application") != -1) {
saveFile(bodyPart.getInputStream(), destDir, decodeText(bodyPart.getFileName()));
}
}
}
} else if (part.isMimeType("message/rfc822")) {
saveAttachment((Part) part.getContent(), destDir);
}
}
/**
* 读取输入流中的数据保存至指定目录
* @param is 输入流
* @param fileName 文件名
* @param destDir 文件存储目录
* @throws FileNotFoundException
* @throws IOException
*/
private static void saveFile(InputStream is, String destDir, String fileName)
throws FileNotFoundException, IOException {
BufferedInputStream bis = new BufferedInputStream(is);
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(new File(destDir + fileName)));
int len = -1;
while ((len = bis.read()) != -1) {
bos.write(len);
bos.flush();
}
bos.close();
bis.close();
}
/**
* 文本解码
* @param encodeText 解码MimeUtility.encodeText(String text)方法编码后的文本
* @return 解码后的文本
* @throws UnsupportedEncodingException
*/
public static String decodeText(String encodeText) throws UnsupportedEncodingException {
if (encodeText == null || "".equals(encodeText)) {
return "";
} else {
return MimeUtility.decodeText(encodeText);
}
}
}
执行结果:
小结
- 使用Java接收Email时,可以用
POP3协议
或IMAP协议
。 - 使用
POP3协议
时,需要用Maven引入JavaMail依赖,并确定POP3服务器的域名/端口/是否使用SSL
等,然后调用相关API接收Email。 - 设置debug模式可以查看通信详细内容,便于排查错误。