Skip to content

Commit 53eeb9a

Browse files
committed
feat(email): expose typed received delivery reports (#763)
1 parent 3b32a94 commit 53eeb9a

33 files changed

Lines changed: 1630 additions & 10 deletions
0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.simplejavamail.api.email;
2+
3+
/** What the reporting mail server says happened to one recipient's delivery. This is not proof of authenticity or that the recipient read the email. */
4+
public enum DeliveryAction {
5+
6+
/** The server reports successful delivery. */
7+
DELIVERED,
8+
/** Delivery has not finished; the server may still be trying. */
9+
DELAYED,
10+
/** The server reports that it has stopped trying to deliver. */
11+
FAILED,
12+
/** The server passed the email onward without a final delivery report. */
13+
RELAYED,
14+
/** The recipient address expanded into other destinations, such as a mailing list. */
15+
EXPANDED,
16+
/** The action was missing, unrecognized or contradictory; inspect the report warnings. */
17+
UNKNOWN
18+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.simplejavamail.api.email;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
import org.jetbrains.annotations.Nullable;
5+
import org.simplejavamail.api.mailer.SmtpServerResponse;
6+
7+
import java.io.Serializable;
8+
9+
/**
10+
* The reporting system's explanation of a delivery result, with its protocol format attached.
11+
* For an interpretable SMTP reply, use the existing response API instead of splitting its text yourself.
12+
* All explanation text remains untrusted and may contain personal information.
13+
*/
14+
public interface DeliveryDiagnostic extends Serializable {
15+
16+
/** The reported explanation format, commonly {@code smtp}. Other systems' diagnostic formats are preserved. */
17+
@NotNull String getType();
18+
19+
/** The explanation without its type prefix. This is source text, not a log-safe message or an application instruction. */
20+
@NotNull String getText();
21+
22+
/**
23+
* The SMTP reply when the diagnostic uses the SMTP format and contains a recognizable reply, otherwise {@code null}.
24+
* An unavailable SMTP view does not discard the original explanation. No reply or delivery status is inferred from human prose.
25+
*/
26+
@Nullable SmtpServerResponse getSmtpResponse();
27+
28+
/** Indicates whether an SMTP view is available without printing the reported type or explanation. */
29+
@Override
30+
@NotNull String toString();
31+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.simplejavamail.api.email;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
import org.jetbrains.annotations.Nullable;
5+
6+
import java.io.Serializable;
7+
import java.util.List;
8+
9+
/**
10+
* Delivery feedback extracted from a received notification Email. Convert MIME/EML through the usual converter and read
11+
* {@link Email#getDeliveryStatusReport()}; applications do not construct these snapshots.
12+
*
13+
* <p>These are the received server's claims, not authenticated proof, a read receipt or instructions to resend. Correlation and business decisions
14+
* remain application-owned. A missing notification does not establish delivery. Optional facts are absent when missing or unusable; warnings explain
15+
* malformed recognized reports without discarding their usable facts. Unambiguous formatting can be recovered with a warning; if optional
16+
* report reading stops, completed facts remain available with an incomplete-extraction warning. Required message-content reads can still fail conversion.</p>
17+
*/
18+
public interface DeliveryStatusReport extends Serializable {
19+
20+
/** The original submission's envelope identifier, when reported. It is separate from Message-ID and is not matched to application records automatically. */
21+
@Nullable String getEnvelopeId();
22+
23+
/** The reporting server and its naming format together, or {@code null} when missing or unusable. */
24+
@Nullable ReportedMailServer getReportingServer();
25+
26+
/** Recipient reports in their received order, including duplicate occurrences. The returned list is immutable and never {@code null}. */
27+
@NotNull List<RecipientDeliveryReport> getRecipients();
28+
29+
/** Human-readable parsing cautions, not machine-readable error codes or a protocol transcript. The returned list is immutable and never {@code null}. */
30+
@NotNull List<String> getWarnings();
31+
32+
/** Summarizes recipient actions and warning counts without identifiers, addresses or diagnostic text. */
33+
@Override
34+
@NotNull String toString();
35+
}

‎modules/core-module/src/main/java/org/simplejavamail/api/email/Email.java‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ public class Email implements Serializable {
227227
*/
228228
@Nullable
229229
private final DeliveryStatusNotification deliveryStatusNotification;
230+
@Nullable
231+
private final DeliveryStatusReport deliveryStatusReport;
230232
private final boolean tlsRequiredForOnwardDelivery;
231233
/** @see EmailPopulatingBuilder#withSendingToAcceptedRecipients(boolean) */
232234
@Nullable
@@ -343,6 +345,7 @@ public Email(@NotNull final EmailPopulatingBuilder builder) {
343345
useReturnReceiptTo = builder.getUseReturnReceiptTo();
344346
returnReceiptTo = builder.getReturnReceiptTo();
345347
deliveryStatusNotification = builder.getDeliveryStatusNotification();
348+
deliveryStatusReport = builder.getDeliveryStatusReport();
346349
tlsRequiredForOnwardDelivery = builder.isTlsRequiredForOnwardDelivery();
347350
sendingToAcceptedRecipients = builder.getSendingToAcceptedRecipients();
348351
overrideReceivers = builder.getOverrideReceivers();
@@ -683,6 +686,22 @@ public boolean isTlsRequiredForOnwardDelivery() {
683686
public Boolean getSendingToAcceptedRecipients() {
684687
return sendingToAcceptedRecipients;
685688
}
689+
690+
/**
691+
* Returns delivery feedback extracted from this Email's original notification, or {@code null} when no accessible outer delivery report was found.
692+
* For example, convert a received bounce with the usual MIME/EML converter, then inspect its recipients and envelope identifier here.
693+
*
694+
* <p>This immutable snapshot describes the source notification, not a changing delivery tracker on the Email originally sent. Copies retain it
695+
* even if their content is edited; new replies and forwards do not inherit it. The getter performs no parsing or I/O. Ordinary outgoing templates
696+
* do not supply it, and conversion back to MIME does not synthesize a delivery report from it.</p>
697+
*
698+
* <p>Received claims are not authenticated delivery proof or permission to retry. If protected content could not be opened, inspect the existing
699+
* protection results as well: {@code null} does not establish that encrypted content contains no report.</p>
700+
*/
701+
@Nullable
702+
public DeliveryStatusReport getDeliveryStatusReport() {
703+
return deliveryStatusReport;
704+
}
686705

687706
/**
688707
* @see EmailStartingBuilder#forwarding(MimeMessage)

‎modules/core-module/src/main/java/org/simplejavamail/api/email/EmailPopulatingBuilder.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1770,6 +1770,12 @@ EmailPopulatingBuilder encryptWithSmime(@NotNull Path pemPath, @Nullable @Cli.Op
17701770
@NotNull
17711771
OriginalSmimeDetails getOriginalSmimeDetails();
17721772

1773+
/**
1774+
* @see Email#getDeliveryStatusReport()
1775+
*/
1776+
@Nullable
1777+
DeliveryStatusReport getDeliveryStatusReport();
1778+
17731779
/**
17741780
* If the Email instance currently being built was the result of a conversion in which the source message was S/MIME
17751781
* signed / encrypted, this field will contain the decrypted MimeMessage instance.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.simplejavamail.api.email;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
5+
import java.io.Serializable;
6+
7+
/**
8+
* A delivery status such as {@code 5.1.1}, already checked and split into its three numeric parts.
9+
* The status class has three possible values; unfamiliar subject/detail combinations remain readable rather than being discarded.
10+
* Read the recipient's {@link DeliveryAction} too: a temporary problem can end in either delay or abandoned delivery.
11+
*/
12+
public interface EnhancedStatusCode extends Serializable {
13+
14+
/** The broad result: {@code 2} for success, {@code 4} for temporary failure or {@code 5} for permanent failure. */
15+
int getStatusClass();
16+
17+
/** The middle part, from zero through 999, describing the area of the problem. For {@code 5.1.1}, this is {@code 1}. */
18+
int getSubject();
19+
20+
/** The final part, from zero through 999, describing the particular condition within that area. */
21+
int getDetail();
22+
23+
/** Whether the code belongs to the success class. This is a reported result, not independently verified delivery. */
24+
boolean isSuccess();
25+
26+
/** Whether the code describes a temporary problem. This alone is not permission for the application to resend. */
27+
boolean isTemporaryFailure();
28+
29+
/** Whether the code describes a permanent problem with sending the message in its current form. */
30+
boolean isPermanentFailure();
31+
32+
/** The complete code, such as {@code 5.1.1}, with no diagnostic prose. */
33+
@Override
34+
@NotNull String toString();
35+
}

‎modules/core-module/src/main/java/org/simplejavamail/api/email/EqualsHelper.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ public static boolean equalsEmail(final Email email1, final Email email2) {
101101
if (!fieldIsEqual(email1.getDeliveryStatusNotification(), email2.getDeliveryStatusNotification(), "deliveryStatusNotification")) {
102102
return false;
103103
}
104+
if (!fieldIsEqual(email1.getDeliveryStatusReport(), email2.getDeliveryStatusReport(), "deliveryStatusReport")) {
105+
return false;
106+
}
104107
if (!fieldIsEqual(email1.getSendingToAcceptedRecipients(), email2.getSendingToAcceptedRecipients(), "sendingToAcceptedRecipients")) {
105108
return false;
106109
}

‎modules/core-module/src/main/java/org/simplejavamail/api/email/ExactEmailBuilder.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ public interface ExactEmailBuilder {
175175
@Nullable
176176
Boolean getSendingToAcceptedRecipients();
177177

178+
/**
179+
* Returns any clear delivery report parsed from the authoritative EML. This does not decrypt or rewrite the original message.
180+
* @see Email#getDeliveryStatusReport()
181+
*/
182+
@Nullable
183+
DeliveryStatusReport getDeliveryStatusReport();
184+
178185
/**
179186
* Builds an {@link Email} whose existing getters expose the parsed EML fields while the original bytes remain authoritative for conversion,
180187
* rehearsal, and sending. At least one explicit envelope recipient is required.

0 commit comments

Comments
 (0)