Skip to content

Commit d1c5134

Browse files
committed
#483: Enhancement: add native support for overriding envelope-level receiver(s)
1 parent 772f596 commit d1c5134

File tree

11 files changed

+141
-30
lines changed

11 files changed

+141
-30
lines changed

Diff for: modules/core-module/src/main/java/org/simplejavamail/api/email/Email.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,17 @@ public class Email implements Serializable {
166166
*/
167167
@Nullable
168168
private final Boolean useReturnReceiptTo;
169-
169+
170170
/**
171171
* @see EmailPopulatingBuilder#withReturnReceiptTo()
172172
* @see EmailPopulatingBuilder#withReturnReceiptTo(Recipient)
173173
*/
174174
private final Recipient returnReceiptTo;
175+
176+
/**
177+
* @see EmailPopulatingBuilder#withOverrideReceivers(Recipient...)
178+
*/
179+
private final List<Recipient> overrideReceivers;
175180

176181
/**
177182
* @see EmailStartingBuilder#forwarding(MimeMessage)
@@ -265,6 +270,7 @@ public Email(@NotNull final EmailPopulatingBuilder builder) {
265270
dispositionNotificationTo = builder.getDispositionNotificationTo();
266271
useReturnReceiptTo = builder.getUseReturnReceiptTo();
267272
returnReceiptTo = builder.getReturnReceiptTo();
273+
overrideReceivers = builder.getOverrideReceivers();
268274
emailToForward = builder.getEmailToForward();
269275
originalSmimeDetails = builder.getOriginalSmimeDetails();
270276
sentDate = builder.getSentDate();
@@ -309,6 +315,10 @@ public String toString() {
309315
s += ",\n\tuseReturnReceiptTo=" + true +
310316
",\n\t\treturnReceiptTo=" + returnReceiptTo;
311317
}
318+
if (!overrideReceivers.isEmpty()) {
319+
s += ",\n\toverrideReceivers=" + true +
320+
",\n\t\toverrideReceivers=" + overrideReceivers;
321+
}
312322
if (!headers.isEmpty()) {
313323
s += ",\n\theaders=" + headers;
314324
}
@@ -442,7 +452,7 @@ public Boolean getUseDispositionNotificationTo() {
442452
public Recipient getDispositionNotificationTo() {
443453
return dispositionNotificationTo;
444454
}
445-
455+
446456
/**
447457
* @see EmailPopulatingBuilder#withReturnReceiptTo()
448458
* @see EmailPopulatingBuilder#withReturnReceiptTo(Recipient)
@@ -451,6 +461,14 @@ public Recipient getDispositionNotificationTo() {
451461
public Boolean getUseReturnReceiptTo() {
452462
return useReturnReceiptTo;
453463
}
464+
465+
/**
466+
* @see EmailPopulatingBuilder#withOverrideReceivers(Recipient...)
467+
*/
468+
@NotNull
469+
public List<Recipient> getOverrideReceivers() {
470+
return overrideReceivers;
471+
}
454472

455473
/**
456474
* @see EmailPopulatingBuilder#withReturnReceiptTo()

Diff for: modules/core-module/src/main/java/org/simplejavamail/api/email/EmailPopulatingBuilder.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.simplejavamail.api.email;
22

33
import jakarta.activation.DataSource;
4+
import jakarta.mail.Address;
45
import jakarta.mail.Message;
56
import jakarta.mail.internet.InternetAddress;
67
import jakarta.mail.internet.MimeMessage;
@@ -1453,6 +1454,18 @@ public interface EmailPopulatingBuilder {
14531454
*/
14541455
EmailPopulatingBuilder withReturnReceiptTo(@NotNull Recipient recipient);
14551456

1457+
/**
1458+
* Delegates to {@link #withOverrideReceivers(List<Recipient>)}.
1459+
*/
1460+
EmailPopulatingBuilder withOverrideReceivers(@NotNull Recipient ...recipients);
1461+
1462+
/**
1463+
* When provided, Simple Java Mail will ignore all recipients set on the email and instead send to the provided recipients only (when calling
1464+
* {@link jakarta.mail.Transport#sendMessage(Message, Address[])}). The actual Message will still contain the original recipients, but won't receive a
1465+
* copy. This is useful for testing purposes.
1466+
*/
1467+
EmailPopulatingBuilder withOverrideReceivers(@NotNull List<Recipient> recipients);
1468+
14561469
/**
14571470
* When an email is sent it is converted to a MimeMessage at which time the sent-date is filled with the current date. With this method
14581471
* this can be fixed to a date of choice.
@@ -1746,13 +1759,19 @@ public interface EmailPopulatingBuilder {
17461759
*/
17471760
@Nullable
17481761
Boolean getUseReturnReceiptTo();
1749-
1762+
17501763
/**
17511764
* @see #withReturnReceiptTo()
17521765
* @see #withReturnReceiptTo(Recipient)
17531766
*/
17541767
@Nullable
17551768
Recipient getReturnReceiptTo();
1769+
1770+
/**
1771+
* @see #withOverrideReceivers(Recipient...)
1772+
*/
1773+
@NotNull
1774+
List<Recipient> getOverrideReceivers();
17561775

17571776
/**
17581777
* @see EmailStartingBuilder#forwarding(MimeMessage)

Diff for: modules/core-module/src/main/java/org/simplejavamail/internal/config/EmailProperty.java

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public enum EmailProperty {
3636
TO_RECIPIENTS(Email::getToRecipients, true),
3737
CC_RECIPIENTS(Email::getCcRecipients, true),
3838
BCC_RECIPIENTS(Email::getBccRecipients, true),
39+
OVERRIDE_RECEIVERS(Email::getOverrideReceivers, true),
3940
SMIME_SIGNING_CONFIG(Email::getPkcs12ConfigForSmimeSigning, false),
4041
SMIME_ENCRYPTION_CONFIG(Email::getX509CertificateForSmimeEncryption, false),
4142
DKIM_SIGNING_CONFIG(Email::getDkimConfig, false),

Diff for: modules/core-module/src/main/java/org/simplejavamail/internal/util/MiscUtil.java

+14
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.Map;
3636
import java.util.Random;
3737
import java.util.regex.Pattern;
38+
import java.util.stream.Collectors;
3839

3940
import static java.lang.Integer.toHexString;
4041
import static java.lang.String.format;
@@ -451,4 +452,17 @@ public static void assignToInstanceField(Object subject, String fieldName, Objec
451452
field.setAccessible(true);
452453
field.set(subject, newValue);
453454
}
455+
456+
@NotNull
457+
public static List<InternetAddress> asInternetAddresses(@NotNull List<Recipient> recipient, @NotNull Charset charset) {
458+
return recipient.stream()
459+
.map(r -> asInternetAddress(r, charset))
460+
.collect(Collectors.toList());
461+
}
462+
463+
@NotNull
464+
@SneakyThrows
465+
public static InternetAddress asInternetAddress(@NotNull Recipient recipient, @NotNull Charset charset) {
466+
return new InternetAddress(recipient.getAddress(), recipient.getName(), charset.name());
467+
}
454468
}

Diff for: modules/simple-java-mail/src/main/java/org/simplejavamail/converter/internal/mimemessage/MimeMessageHelper.java

+19-23
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@
2525
import org.simplejavamail.internal.util.NamedDataSource;
2626

2727
import java.io.UnsupportedEncodingException;
28+
import java.nio.charset.Charset;
2829
import java.nio.charset.StandardCharsets;
2930
import java.util.Collection;
3031
import java.util.Map;
3132
import java.util.UUID;
3233

3334
import static java.lang.Boolean.TRUE;
3435
import static java.lang.String.format;
36+
import static java.nio.charset.StandardCharsets.UTF_8;
3537
import static java.util.Objects.requireNonNull;
3638
import static java.util.Optional.ofNullable;
3739
import static org.simplejavamail.internal.util.MiscUtil.valueNullOrEmpty;
@@ -45,22 +47,18 @@ public class MimeMessageHelper {
4547
/**
4648
* Encoding used for setting body text, email address, headers, reply-to fields etc. ({@link StandardCharsets#UTF_8}).
4749
*/
48-
private static final String CHARACTER_ENCODING = StandardCharsets.UTF_8.name();
50+
private static final Charset CHARACTER_ENCODING = UTF_8;
4951

5052
private static final String HEADER_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
5153

52-
private MimeMessageHelper() {
53-
54-
}
55-
5654
static void setSubject(@NotNull final Email email, final MimeMessage message) throws MessagingException {
57-
message.setSubject(email.getSubject(), CHARACTER_ENCODING);
55+
message.setSubject(email.getSubject(), CHARACTER_ENCODING.name());
5856
}
5957

60-
static void setFrom(@NotNull final Email email, final MimeMessage message) throws UnsupportedEncodingException, MessagingException {
58+
static void setFrom(@NotNull final Email email, final MimeMessage message) throws MessagingException {
6159
val fromRecipient = email.getFromRecipient();
6260
if (fromRecipient != null) {
63-
message.setFrom(new InternetAddress(fromRecipient.getAddress(), fromRecipient.getName(), CHARACTER_ENCODING));
61+
message.setFrom(MiscUtil.asInternetAddress(fromRecipient, CHARACTER_ENCODING));
6462
}
6563
}
6664

@@ -69,13 +67,12 @@ static void setFrom(@NotNull final Email email, final MimeMessage message) throw
6967
*
7068
* @param email The message in which the recipients are defined.
7169
* @param message The javax message that needs to be filled with recipients.
72-
* @throws UnsupportedEncodingException See {@link InternetAddress#InternetAddress(String, String)}.
7370
* @throws MessagingException See {@link Message#addRecipient(Message.RecipientType, Address)}
7471
*/
7572
static void setRecipients(final Email email, final Message message)
76-
throws UnsupportedEncodingException, MessagingException {
73+
throws MessagingException {
7774
for (final Recipient recipient : email.getRecipients()) {
78-
message.addRecipient(recipient.getType(), new InternetAddress(recipient.getAddress(), recipient.getName(), CHARACTER_ENCODING));
75+
message.addRecipient(recipient.getType(), MiscUtil.asInternetAddress(recipient, CHARACTER_ENCODING));
7976
}
8077
}
8178

@@ -84,16 +81,15 @@ static void setRecipients(final Email email, final Message message)
8481
*
8582
* @param email The message in which the recipients are defined.
8683
* @param message The javax message that needs to be filled with reply-to addresses.
87-
* @throws UnsupportedEncodingException See {@link InternetAddress#InternetAddress(String, String)}.
8884
* @throws MessagingException See {@link Message#setReplyTo(Address[])}
8985
*/
9086
static void setReplyTo(@NotNull final Email email, final Message message)
91-
throws UnsupportedEncodingException, MessagingException {
87+
throws MessagingException {
9288
if (!email.getReplyToRecipients().isEmpty()) {
9389
val replyToAddresses = new Address[email.getReplyToRecipients().size()];
9490
int i = 0;
9591
for (val replyToRecipient : email.getReplyToRecipients()) {
96-
replyToAddresses[i++] = new InternetAddress(replyToRecipient.getAddress(), replyToRecipient.getName(), CHARACTER_ENCODING);
92+
replyToAddresses[i++] = MiscUtil.asInternetAddress(replyToRecipient, CHARACTER_ENCODING);
9793
}
9894
message.setReplyTo(replyToAddresses);
9995
}
@@ -110,20 +106,20 @@ static void setTexts(@NotNull final Email email, final MimeMultipart multipartAl
110106
throws MessagingException {
111107
if (email.getPlainText() != null) {
112108
val messagePart = new MimeBodyPart();
113-
messagePart.setText(email.getPlainText(), CHARACTER_ENCODING);
109+
messagePart.setText(email.getPlainText(), CHARACTER_ENCODING.name());
114110
messagePart.addHeader(HEADER_CONTENT_TRANSFER_ENCODING, determineContentTransferEncoder(email));
115111
multipartAlternativeMessages.addBodyPart(messagePart);
116112
}
117113
if (email.getHTMLText() != null) {
118114
val messagePartHTML = new MimeBodyPart();
119-
messagePartHTML.setContent(email.getHTMLText(), format("text/html; charset=\"%s\"", CHARACTER_ENCODING));
115+
messagePartHTML.setContent(email.getHTMLText(), format("text/html; charset=\"%s\"", CHARACTER_ENCODING.name()));
120116
messagePartHTML.addHeader(HEADER_CONTENT_TRANSFER_ENCODING, determineContentTransferEncoder(email));
121117
multipartAlternativeMessages.addBodyPart(messagePartHTML);
122118
}
123119
if (email.getCalendarText() != null) {
124120
val calendarMethod = requireNonNull(email.getCalendarMethod(), "calendarMethod is required when calendarText is set");
125121
val messagePartCalendar = new MimeBodyPart();
126-
messagePartCalendar.setContent(email.getCalendarText(), format("text/calendar; charset=\"%s\"; method=\"%s\"", CHARACTER_ENCODING, calendarMethod));
122+
messagePartCalendar.setContent(email.getCalendarText(), format("text/calendar; charset=\"%s\"; method=\"%s\"", CHARACTER_ENCODING.name(), calendarMethod));
127123
messagePartCalendar.addHeader(HEADER_CONTENT_TRANSFER_ENCODING, determineContentTransferEncoder(email));
128124
multipartAlternativeMessages.addBodyPart(messagePartCalendar);
129125
}
@@ -146,14 +142,14 @@ private static String determineContentTransferEncoder(@NotNull Email email) {
146142
static void setTexts(@NotNull final Email email, final MimePart messagePart)
147143
throws MessagingException {
148144
if (email.getPlainText() != null) {
149-
messagePart.setText(email.getPlainText(), CHARACTER_ENCODING);
145+
messagePart.setText(email.getPlainText(), CHARACTER_ENCODING.name());
150146
}
151147
if (email.getHTMLText() != null) {
152-
messagePart.setContent(email.getHTMLText(), format("text/html; charset=\"%s\"", CHARACTER_ENCODING));
148+
messagePart.setContent(email.getHTMLText(), format("text/html; charset=\"%s\"", CHARACTER_ENCODING.name()));
153149
}
154150
if (email.getCalendarText() != null) {
155151
val calendarMethod = requireNonNull(email.getCalendarMethod(), "CalendarMethod must be set when CalendarText is set");
156-
messagePart.setContent(email.getCalendarText(), format("text/calendar; charset=\"%s\"; method=\"%s\"", CHARACTER_ENCODING, calendarMethod));
152+
messagePart.setContent(email.getCalendarText(), format("text/calendar; charset=\"%s\"; method=\"%s\"", CHARACTER_ENCODING.name(), calendarMethod));
157153
}
158154
messagePart.addHeader(HEADER_CONTENT_TRANSFER_ENCODING, determineContentTransferEncoder(email));
159155
}
@@ -225,21 +221,21 @@ static void setHeaders(@NotNull final Email email, final Message message)
225221

226222
if (TRUE.equals(email.getUseDispositionNotificationTo())) {
227223
final Recipient dispositionTo = checkNonEmptyArgument(email.getDispositionNotificationTo(), "dispositionNotificationTo");
228-
final Address address = new InternetAddress(dispositionTo.getAddress(), dispositionTo.getName(), CHARACTER_ENCODING);
224+
final Address address = MiscUtil.asInternetAddress(dispositionTo, CHARACTER_ENCODING);
229225
message.setHeader("Disposition-Notification-To", address.toString());
230226
}
231227

232228
if (TRUE.equals(email.getUseReturnReceiptTo())) {
233229
final Recipient returnReceiptTo = checkNonEmptyArgument(email.getReturnReceiptTo(), "returnReceiptTo");
234-
final Address address = new InternetAddress(returnReceiptTo.getAddress(), returnReceiptTo.getName(), CHARACTER_ENCODING);
230+
final Address address = MiscUtil.asInternetAddress(returnReceiptTo, CHARACTER_ENCODING);
235231
message.setHeader("Return-Receipt-To", address.toString());
236232
}
237233
}
238234

239235
private static void setHeader(Message message, Map.Entry<String, Collection<String>> header) throws UnsupportedEncodingException, MessagingException {
240236
for (final String headerValue : header.getValue()) {
241237
final String headerName = header.getKey();
242-
final String headerValueEncoded = MimeUtility.encodeText(headerValue, CHARACTER_ENCODING, null);
238+
final String headerValueEncoded = MimeUtility.encodeText(headerValue, CHARACTER_ENCODING.name(), null);
243239
final String foldedHeaderValue = MimeUtility.fold(headerName.length() + 2, headerValueEncoded);
244240
message.addHeader(header.getKey(), foldedHeaderValue);
245241
}

Diff for: modules/simple-java-mail/src/main/java/org/simplejavamail/email/internal/EmailPopulatingBuilderImpl.java

+34-1
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,11 @@ public class EmailPopulatingBuilderImpl implements InternalEmailPopulatingBuilde
310310
@Nullable
311311
private Recipient returnReceiptTo;
312312

313+
/**
314+
* @see #withOverrideReceivers(Recipient...)
315+
*/
316+
private final List<Recipient> overrideReceivers = new ArrayList<>();
317+
313318
/**
314319
* @see EmailBuilder#forwarding(MimeMessage)
315320
*/
@@ -2079,6 +2084,25 @@ public EmailPopulatingBuilder withReturnReceiptTo(@NotNull final Recipient recip
20792084
return this;
20802085
}
20812086

2087+
/**
2088+
* @see EmailPopulatingBuilder#withReturnReceiptTo(Recipient)
2089+
*/
2090+
@Override
2091+
public EmailPopulatingBuilder withOverrideReceivers(@NotNull Recipient ...recipients) {
2092+
checkNonEmptyArgument(recipients, "recipients");
2093+
return withOverrideReceivers(asList(recipients));
2094+
}
2095+
2096+
/**
2097+
* @see EmailPopulatingBuilder#withOverrideReceivers(List)
2098+
*/
2099+
@Override
2100+
public EmailPopulatingBuilder withOverrideReceivers(@NotNull List<Recipient> recipients) {
2101+
checkNonEmptyArgument(recipients, "recipients");
2102+
this.overrideReceivers.addAll(recipients);
2103+
return this;
2104+
}
2105+
20822106
/**
20832107
* @see EmailPopulatingBuilder#getOriginalSmimeDetails()
20842108
*/
@@ -2524,7 +2548,7 @@ public Recipient getDispositionNotificationTo() {
25242548
public Boolean getUseReturnReceiptTo() {
25252549
return useReturnReceiptTo;
25262550
}
2527-
2551+
25282552
/**
25292553
* @see EmailPopulatingBuilder#getReturnReceiptTo()
25302554
*/
@@ -2533,6 +2557,15 @@ public Boolean getUseReturnReceiptTo() {
25332557
public Recipient getReturnReceiptTo() {
25342558
return returnReceiptTo;
25352559
}
2560+
2561+
/**
2562+
* @see EmailPopulatingBuilder#getOverrideReceivers()
2563+
*/
2564+
@Override
2565+
@NotNull
2566+
public List<Recipient> getOverrideReceivers() {
2567+
return overrideReceivers;
2568+
}
25362569

25372570
/**
25382571
* @see EmailPopulatingBuilder#getEmailToForward()

Diff for: modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/EmailGovernanceImpl.java

+4
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ public Email produceEmailApplyingDefaultsAndOverrides(@Nullable Email provided)
240240
}
241241
}
242242

243+
val overrideReceivers = this.<Recipient>resolveEmailCollectionProperty(provided, EmailProperty.OVERRIDE_RECEIVERS);
244+
if (!overrideReceivers.isEmpty()) {
245+
builder.withOverrideReceivers(overrideReceivers);
246+
}
243247
ofNullable(this.<ContentTransferEncoding>resolveEmailProperty(provided, EmailProperty.CONTENT_TRANSFER_ENCODING)).ifPresent(builder::withContentTransferEncoding);
244248
ofNullable(this.<Pkcs12Config>resolveEmailProperty(provided, EmailProperty.SMIME_SIGNING_CONFIG)).ifPresent(builder::signWithSmime);
245249
ofNullable(this.<X509Certificate>resolveEmailProperty(provided, EmailProperty.SMIME_ENCRYPTION_CONFIG)).ifPresent(builder::encryptWithSmime);

Diff for: modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/util/TransportRunner.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
import jakarta.mail.MessagingException;
44
import jakarta.mail.Session;
55
import jakarta.mail.Transport;
6+
import jakarta.mail.internet.InternetAddress;
67
import lombok.val;
78
import org.jetbrains.annotations.NotNull;
89
import org.simplejavamail.api.email.Email;
910
import org.simplejavamail.api.internal.batchsupport.LifecycleDelegatingTransport;
1011
import org.simplejavamail.internal.moduleloader.ModuleLoader;
1112
import org.simplejavamail.internal.modules.BatchModule;
13+
import org.simplejavamail.internal.util.MiscUtil;
1214
import org.simplejavamail.mailer.internal.SessionBasedEmailToMimeMessageConverter;
1315
import org.slf4j.Logger;
1416

1517
import java.util.UUID;
1618

19+
import static java.nio.charset.StandardCharsets.UTF_8;
1720
import static org.slf4j.LoggerFactory.getLogger;
1821

1922
/**
@@ -37,7 +40,10 @@ public static void sendMessage(@NotNull final UUID clusterKey, final Session ses
3740
throws MessagingException {
3841
runOnSessionTransport(clusterKey, session, false, (transport, actualSessionUsed) -> {
3942
val message = SessionBasedEmailToMimeMessageConverter.convertAndLogMimeMessage(actualSessionUsed, email);
40-
transport.sendMessage(message, message.getAllRecipients());
43+
val actualRecipients = email.getOverrideReceivers().isEmpty()
44+
? message.getAllRecipients()
45+
: MiscUtil.asInternetAddresses(email.getOverrideReceivers(), UTF_8).toArray(new InternetAddress[0]);
46+
transport.sendMessage(message, actualRecipients);
4147
LOGGER.trace("...email sent");
4248
});
4349
}

0 commit comments

Comments
 (0)