Skip to content

Commit c9df087

Browse files
committed
add inline attachment support
1 parent d4c2f2d commit c9df087

7 files changed

Lines changed: 291 additions & 31 deletions

File tree

readme.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,29 @@ Add the mailer service to the classpath by adding the following maven dependency
2020
If you want to use attachments, you will have to create a bean implementing this interface: [AttachmentDataSource.java](src%2Fmain%2Fjava%2Fit%2Faboutbits%2Fspringboot%2Femailservice%2Flib%2FAttachmentDataSource.java)
2121
This step is optional.
2222

23+
#### Inline (CID) attachments
24+
25+
To embed an attachment inline, set a `contentId` on the attachment and reference it in the `htmlBody` via the `cid:` scheme.
26+
This is the equivalent of `MimeMessageHelper.addInline(...)` and renders in all major email clients.
27+
28+
```java
29+
// @formatter:off
30+
EmailParameter.Email.builder()
31+
// ...
32+
.htmlBody("<img src=\"cid:header-logo\"><h1>Hello!</h1>")
33+
.attachment(EmailParameter.Email.Attachment.builder()
34+
.contentId("header-logo")
35+
.fileName("logo.png")
36+
.contentType("image/png")
37+
.payload(new ClassPathResource("/templates/mail/images/logo.png").getInputStream())
38+
.build())
39+
.build();
40+
// @formatter:on
41+
```
42+
43+
Attachments without a `contentId` are added as regular attachments. Each `contentId` must be referenced in the
44+
`htmlBody` as `cid:contentId`, otherwise validation fails.
45+
2346
## Usage
2447

2548
### Sending an Email

src/main/java/it/aboutbits/springboot/emailservice/lib/EmailAttachmentDto.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import it.aboutbits.springboot.emailservice.lib.model.Email;
44
import org.jspecify.annotations.NullMarked;
5+
import org.jspecify.annotations.Nullable;
56

67
@NullMarked
78
public record EmailAttachmentDto(
@@ -13,6 +14,9 @@ public record EmailAttachmentDto(
1314

1415
String contentType,
1516

17+
@Nullable
18+
String contentId,
19+
1620
long fileReference
1721
) {
1822
}

src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailParameter.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package it.aboutbits.springboot.emailservice.lib.application;
22

3+
import jakarta.validation.Valid;
4+
import jakarta.validation.constraints.AssertTrue;
35
import jakarta.validation.constraints.NotBlank;
46
import jakarta.validation.constraints.NotEmpty;
57
import lombok.Builder;
@@ -10,12 +12,14 @@
1012
import java.io.InputStream;
1113
import java.time.OffsetDateTime;
1214
import java.util.List;
15+
import java.util.Objects;
1316
import java.util.Set;
1417

1518
@Builder
1619
@NullMarked
1720
public record EmailParameter(
1821
OffsetDateTime scheduledAt,
22+
@Valid
1923
Email email
2024
) {
2125
@Builder
@@ -41,15 +45,27 @@ public record Email(
4145
String replyToName,
4246

4347
@Singular
48+
@Valid
4449
Set<Attachment> attachments
4550
) {
51+
@AssertTrue(message = "each inline attachment contentId must be referenced in the htmlBody as cid:contentId")
52+
public boolean isInlineAttachmentsValid() {
53+
return attachments.stream()
54+
.map(Attachment::contentId)
55+
.filter(Objects::nonNull)
56+
.allMatch(contentId -> htmlBody.contains("cid:" + contentId));
57+
}
58+
4659
@Builder
4760
public record Attachment(
4861
InputStream payload,
4962
@NotBlank
5063
String fileName,
5164
@NotBlank
52-
String contentType
65+
String contentType,
66+
// if set, the attachment is embedded inline and can be referenced in the htmlBody as "cid:contentId"
67+
@Nullable
68+
String contentId
5369
) {
5470
}
5571
}

src/main/java/it/aboutbits/springboot/emailservice/lib/application/EmailServiceMigrator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ updated_at timestamp with time zone default now() not null,
7676
7777
alter table email_service_emails add column if not exists cleanup_start_time timestamp with time zone;
7878
79+
alter table email_service_email_attachments add column if not exists content_id text;
80+
7981
update email_service_emails
8082
set execution_end_time = sent_at
8183
where sent_at is not null

src/main/java/it/aboutbits/springboot/emailservice/lib/application/ManageEmail.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,17 @@ private void sendMail(
209209
}
210210

211211
for (var attachment : attachments) {
212-
var payload = attachmentDataSource.getAttachmentPayload(attachment.getFileReference());
213-
helper.addAttachment(attachment.getFileName(), new ByteArrayResource(payload.readAllBytes()));
214-
payload.close();
212+
ByteArrayResource resource;
213+
try (var payload = attachmentDataSource.getAttachmentPayload(attachment.getFileReference())) {
214+
resource = new ByteArrayResource(payload.readAllBytes());
215+
}
216+
217+
var contentId = attachment.getContentId();
218+
if (contentId != null) {
219+
helper.addInline(contentId, resource, attachment.getContentType());
220+
} else {
221+
helper.addAttachment(attachment.getFileName(), resource);
222+
}
215223
}
216224

217225
mailSender.send(message);
@@ -241,6 +249,7 @@ private Email fromParameter(EmailParameter parameter) throws AttachmentException
241249
var emailAttachment = new EmailAttachment();
242250
emailAttachment.setEmail(email);
243251
emailAttachment.setContentType(attachment.contentType());
252+
emailAttachment.setContentId(attachment.contentId());
244253
emailAttachment.setFileName(attachment.fileName());
245254
emailAttachment.setFileReference(reference);
246255

src/main/java/it/aboutbits/springboot/emailservice/lib/model/EmailAttachment.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,7 @@ public class EmailAttachment {
2929

3030
private String contentType;
3131

32+
private String contentId;
33+
3234
private long fileReference;
3335
}

0 commit comments

Comments
 (0)