1. 概述

虽然图片通常以附件形式添加到邮件中,但也可以直接嵌入邮件正文。这种方式特别适合需要直接展示视觉内容的场景,让收件人无需单独下载即可查看图片。

Java Mail API 通过 MimeBodyPartMimeMultipart 类提供了在邮件正文中嵌入图片的机制

本教程将探讨如何使用这两个类实现图片内联,包括如何通过内容ID(Content-ID)识别图片并在HTML正文中引用该ID。最后,我们还会使用GreenMail库作为模拟SMTP服务器编写单元测试。

2. 项目配置

首先用Maven创建一个简单的Java项目。在pom.xml中添加以下依赖:

<dependency>
    <groupId>org.eclipse.angus</groupId>
    <artifactId>angus-mail</artifactId>
    <version>2.0.3</version>
</dependency>
<dependency>
    <groupId>com.icegreen</groupId>
    <artifactId>greenmail</artifactId>
    <version>2.1.3</version>
</dependency>
  • angus-mail 提供 MimeMessageMimeMultipart 等核心类用于构建多部分邮件
  • greenmail 让我们能快速启动模拟SMTP服务器进行测试

接下来定义SMTP连接参数:

private final String USERNAME = "your_smtp_username";
private final String PASSWORD = "your_smtp_password";
private final String HOST = "smtp.example.com";
private final String PORT = "587";

⚠️ 这里可以使用任何支持标准SMTP协议的服务,比如Amazon SES或Azure Communication Service。

最后将示例图片 java.png 放到项目的 resources 目录下。

3. 使用 MimeMultipartMimeBodyPart 内联图片

发送邮件需要几个关键步骤:定义SMTP连接属性 → 创建会话 → 构建多部分消息 → 发送邮件。

3.1. 定义连接属性

创建 smtpProperties() 方法封装SMTP配置:

Properties smtpProperties() {
    Properties prop = new Properties();
    prop.put("mail.smtp.auth", "true");
    prop.put("mail.smtp.starttls.enable", "true");
    prop.put("mail.smtp.host", HOST);
    prop.put("mail.smtp.port", PORT);
    prop.put("mail.smtp.user", USERNAME);
    prop.put("mail.smtp.password", PASSWORD);

    return prop;
}

这里通过键值对配置了SMTP认证、TLS加密和服务器地址等基础参数。

3.2. 创建会话实例

使用配置属性创建认证会话:

Session smtpsession(Properties props) {
    final String username = props.getProperty("mail.smtp.user");
    final String password = props.getProperty("mail.smtp.password");

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

    return session;
}

Session 对象封装了与SMTP服务器的连接,需要传入连接属性和认证凭据。

3.3. 发送带内联图片的邮件

核心实现方法如下:

void sendEmail(Session session, String to, String subject, String body, String filePath) 
  throws MessagingException, IOException {
    MimeMessage message = new MimeMessage(session);
    message.setFrom(new InternetAddress("sender@example.com"));
    message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
    message.setSubject(subject);

    MimeBodyPart htmlPart = new MimeBodyPart();
    htmlPart.setContent(body, "text/html");

    MimeBodyPart imagePart = new MimeBodyPart();
    imagePart.attachFile(new File(filePath));
    imagePart.setContentID("<image1>");
    imagePart.setDisposition(MimeBodyPart.INLINE);

    MimeMultipart mimeMultipart = new MimeMultipart("related");
    mimeMultipart.addBodyPart(htmlPart);
    mimeMultipart.addBodyPart(imagePart);

    message.setContent(mimeMultipart);
    Transport.send(message);
}

关键点解析:

  1. 创建两个 MimeBodyPart:HTML正文和图片部分
  2. 为图片设置唯一ID(<image1>)和内联 disposition
  3. 使用 cid: 协议在HTML中引用图片ID
  4. related 类型的 MimeMultipart 组合各部分

踩坑提示:如果需要内联多张图片,必须为每张图片创建独立的 MimeBodyPart 并分配唯一ID。

3.4. 调用示例

实际发送邮件的代码:

InlineImage inlineImage = new InlineImage();
Properties properties = inlineImage.smtpProperties();
Session session = inlineImage.smtpsession(properties);

String to = "receiver@example.com";
String subject = "Baeldung";
String body = """
    <p>Welcome to Baeldung, home of Java and its frameworks.</p>
    <img src='cid:image1'></img>
    <p> Explore and learn. </p>
   """;
String imagePath = "src/main/resources/image/java.png";

inlineImage.sendEmail(session, to, subject, body, imagePath);

⚠️ HTML中必须使用 cid:image1 格式引用图片,如果 cid 前缀缺失或ID不匹配,图片会变成附件而非内联显示。

邮件效果预览: 邮件内联图片效果

4. 单元测试

直接使用生产SMTP服务器测试很麻烦,用GreenMail模拟服务器更高效。

4.1. 创建测试会话

为GreenMail定制会话创建方法:

Session smtpsession(GreenMail greenMail) {
    Session session = greenMail.getSmtp().createSession();
    return session;
}

4.2. 测试用例

完整测试代码:

@Test
void givenHtmlEmailWithInlineImage_whenSentViaGreenMailSmtp_thenReceivesEmailWithInlineImage() throws Exception {
    GreenMail greenMail = new GreenMail(ServerSetupTest.SMTP);
    greenMail.start();
    InlineImage inlineImage = new InlineImage();
    Session session = inlineImage.smtpsession(greenMail);

    String to = "receiver@localhost";
    String subject = "Test Subject";
    String body = """
         <p>Welcome to Baeldung, home of Java and its frameworks.</p>
         <img src='cid:image1'></img>
         <p> Explore and learn. </p>
        """;
    String imagePath = "src/main/resources/image/java.png";

    inlineImage.sendEmail(session, to, subject, body, imagePath);

    MimeMessage[] receivedMessages = greenMail.getReceivedMessages();
    assertEquals(1, receivedMessages.length);

    MimeMessage message = receivedMessages[0];

    Multipart multipart = (Multipart) message.getContent();
    assertEquals(2, multipart.getCount());

    BodyPart htmlPart = multipart.getBodyPart(0);
    assertTrue(htmlPart.getContentType().contains("text/html"));
    String htmlContent = (String) htmlPart.getContent();
    assertTrue(htmlContent.contains("cid:image1"));

    BodyPart imagePart = multipart.getBodyPart(1);

    assertEquals(Part.INLINE, imagePart.getDisposition());
    greenMail.stop();
}

验证要点:

  1. 邮件包含两个部分(HTML正文 + 图片)
  2. HTML部分包含 cid:image1 引用
  3. 图片部分的 disposition 确认为 INLINE

5. 总结

本文通过多部分消息结构实现了邮件图片内联,核心在于:

  1. 为图片分配唯一Content-ID
  2. 使用 cid: URI在HTML中引用该ID
  3. related 类型的 MimeMultipart 组合内容

完整示例代码可在 GitHub仓库 获取。