Memra

Attachments: MimeMultipart and MimeBodyPart

◈ 5 cards

A message with an attachment is a tree — a multipart/mixed container holding a text/plain part and one part per file — built with MimeBodyPart, attachFile and setContent.

A message with an attachment is a tree

A plain message is flat: one Content-Type: text/plain header and a body of characters. Attach a file and the body stops being text and becomes a container. The message header changes to Content-Type: multipart/mixed; boundary="=_Part_0_1739", and the body becomes a sequence of parts, each separated by that boundary line and each carrying its own small set of headers — its own content type, its own encoding, its own filename.

So a message is a tree. The root is the message, its child is a multipart container, and the container's children are the parts. Nothing stops a part from being another multipart, which is how a message with both an HTML alternative and an attachment ends up two levels deep.

Which subtype you choose is a statement about the relationship between the parts, and choosing wrongly is why an attachment sometimes vanishes:

  • mixed — independent parts, displayed in order. Text plus attachments.
  • alternative — the same content expressed twice, and the client picks one. Plain text plus HTML.
  • related — parts that reference each other by Content-ID. HTML plus its inline images.

Send your text and your PNG as multipart/alternative and a compliant client will show one and discard the other, because you told it they were two renderings of one thing. The assignment wants mixed, which is also what new MimeMultipart() gives you by default.

Building the tree

Three classes, and the shape follows the diagram exactly. MimeMultipart is the container. MimeBodyPart is a part. setContent grafts the container onto the message.

The text part is new MimeBodyPart() followed by setText(body) — the same call you used on the message itself in the last lesson, because MimeMessage and MimeBodyPart implement the same Part interface.

The file part has a one-line form, attachFile(new File(path)), which does three things: it wraps the file in a FileDataSource, sets Content-Disposition: attachment, and sets the filename from the file's own name. It throws IOException as well as MessagingException, because unlike everything else in the API it really does read from disk.

Then addBodyPart twice and msg.setContent(mixed). Order matters: most clients render the first part as the message body, so add the text before the file.

Content type and transfer encoding

Two headers on the attachment part decide whether the file survives.

The content type is guessed from the extension by a FileTypeMap. A .png becomes image/png; an extension the map does not recognise falls back to application/octet-stream, which is harmless but unhelpful. When the type matters, pin it — build the DataHandler yourself, or use the overload of attachFile that takes a content type.

The content-transfer-encoding is the one that actually matters. SMTP's DATA phase is a line-oriented, historically 7-bit channel: arbitrary bytes cannot travel through it unchanged. So when Transport.send calls saveChanges(), the message walks its own tree and each part chooses an encoding — 7bit or quoted-printable for text, base64 for binary. That is why a PNG arrives intact: it is re-expressed as ASCII, at about a third more bytes, and decoded at the far end.

You never write the boundary string. Jakarta Mail generates one that does not occur inside any part, which is a real obligation — a boundary that appeared in the data would split the message in the wrong place.

Worked example — the same program, now with a file

The assignment's second program is the first program plus one optional key in the input file:

Subject: lab notes
Attachment: charts/october.png
Body: Numbers attached, as promised.

Because Body: still runs to EOF, Attachment: must appear above it — a good thing to state in your test plan rather than discover. The send path then forks on one condition: no attachment key and the message keeps setText; an attachment key and the body becomes a two-part multipart/mixed.

MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(body);
MimeBodyPart filePart = new MimeBodyPart();
filePart.attachFile(new File(attachmentPath));
MimeMultipart mixed = new MimeMultipart();
mixed.addBodyPart(textPart);
mixed.addBodyPart(filePart);
msg.setContent(mixed);

Everything else — the session, the recipients, Transport.send — is untouched. Test it with a PNG and a ZIP, and verify by opening the received attachment, not by watching the program exit cleanly: a corrupted attachment sends perfectly happily.

part 1part 2multipart/mixednew MimeMultipart()text/plainsetText, 7bitimage/pngattachFile, base64Boundary lines separate the parts onthe wire.
setContent(mixed) replaces the message body with this whole subtree. The first part is what most clients render as the message text, so add it first.
subtypeassertsuse formixedindependent parts, in ordertext plus attachmentsalternativeone content, two renderingsplain text plus HTMLrelatedparts reference each otherHTML plus inline imagesnew MimeMultipart() defaults to mixed.
The subtype is a claim about the parts, not a formatting choice. Sending text and a PNG as alternative tells the client they are two renderings of one thing, so it shows one and drops the other.
NORMAL ~/memra/learn/comp-348/javamail-attachments-multipart utf-8 LF