spring-projects / spring-projects/spring-framework

Add fluent MimeMessage builder as a modern alternative to MimeMessageHelper

Open
#37,013 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

in: core status: waiting-for-triage
Dominant language
Java
Stars
60.2k
Forks
38.8k
Avg merge
5d 2h
Merged PRs (30d)
27

Description

What I'd like

A fluent way to build MIME messages without the ceremony that MimeMessageHelper requires:

var message = MimeMessageBuilder.builder()
    .from("notification@example.com", "App Name")
    .to(List.of("recipient@example.com"))
    .cc(ccList)
    .subject("Hello")
    .text("Plain text fallback")
    .html("<h1>Hello</h1>")
    .attachment("report.pdf", bytes, "application/pdf")
    .header("X-Custom", "value")
    .build();

javaMailSender.send(message);
What we do today

Every project I've seen (including ours) ends up with something like this:

javaMailSender.send(mimeMessage -> {
    var multipart = hasText(req.getHtml()) || !req.getAttachments().isEmpty();
    var helper = new MimeMessageHelper(mimeMessage, multipart, StandardCharsets.UTF_8.name());

    helper.setTo(req.getTo().toArray(String[]::new));
    if (req.getCc() != null) {
        helper.setCc(req.getCc().toArray(String[]::new));
    }
    if (req.getBcc() != null) {
        helper.setBcc(req.getBcc().toArray(String[]::new));
    }

    if (hasText(req.getHtml()) && hasText(req.getText())) {
        helper.setText(req.getText(), req.getHtml());
    } else if (hasText(req.getHtml())) {
        helper.setText(req.getHtml(), true);
    } else {
        helper.setText(req.getText());
    }

    if (hasText(req.getFromName())) {
        helper.setFrom(req.getFrom(), req.getFromName());
    } else {
        helper.setFrom(req.getFrom());
    }

    if (hasText(req.getSubject())) helper.setSubject(req.getSubject());
    if (hasText(req.getReplyTo())) helper.setReplyTo(req.getReplyTo());
    // attachments...
});

It's not terrible, but it's a lot of defensive code for something that should be straightforward. The main annoyances:

  • You have to figure out the multipart flag yourself before you even start
  • setCc(null) throws, so every optional field needs a null check
  • Lists need toArray(String[]::new) every time
  • Text vs HTML vs both requires a three-way branch
  • None of this is really "our" logic — it's just wiring
Suggested behavior
  • Multipart mode figured out automatically from whether there's HTML/attachments
  • Null or empty optional fields just get ignored (no exception)
  • Takes collections directly
  • If both .text() and .html() are called, it does the right thing (multipart/alternative)
  • UTF-8 by default

I think of it as the same kind of improvement RestClient brought over RestTemplate — the imperative API is still there, but the common case gets much nicer.

Happy to take a stab at a PR if there's interest.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the existing MimeMessageHelper usage and the RestClient/RestTemplate relationship described in the issue. Define the builder's scope around the shown from, recipient, subject, text, HTML, attachment, and header operations, then verify that UTF-8 defaults, optional-field handling, multipart selection, and combined text/HTML behavior are covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
backend, backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.