diff --git a/FAQ.md b/FAQ.md
index ae91404a7b..7b1e8e3fca 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -2863,6 +2863,10 @@ Please see [this FAQ](#user-content-faq163) for details.
Since this is an experimental feature, my advice is to start with just one folder.
+*Send user unknown (version 1.1477+)*
+
+Send a [Delivery Status Notification](https://tools.ietf.org/html/rfc3464) *User unknown* via the answer menu.
+
diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java
index fa87c88082..3f250fc5b2 100644
--- a/app/src/main/java/eu/faircode/email/AdapterMessage.java
+++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java
@@ -3349,7 +3349,8 @@ public class AdapterMessage extends RecyclerView.Adapter= Build.VERSION_CODES.O && !BuildConfig.DEBUG)
diff --git a/app/src/main/java/eu/faircode/email/EntityMessage.java b/app/src/main/java/eu/faircode/email/EntityMessage.java
index d41ca5e8cf..5d87f4c58a 100644
--- a/app/src/main/java/eu/faircode/email/EntityMessage.java
+++ b/app/src/main/java/eu/faircode/email/EntityMessage.java
@@ -104,6 +104,7 @@ public class EntityMessage implements Serializable {
static final Integer DSN_NONE = 0;
static final Integer DSN_RECEIPT = 1;
+ static final Integer DSN_USER_UNKNOWN = 2;
static final Long SWIPE_ACTION_ASK = -1L;
static final Long SWIPE_ACTION_SEEN = -2L;
diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java
index bb81b2d640..e78c4075d9 100644
--- a/app/src/main/java/eu/faircode/email/FragmentCompose.java
+++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java
@@ -1184,6 +1184,7 @@ public class FragmentCompose extends FragmentBase {
args.putLong("account", a.getLong("account", -1));
args.putLong("identity", a.getLong("identity", -1));
args.putLong("reference", a.getLong("reference", -1));
+ args.putInt("dsn", a.getInt("dsn", -1));
args.putSerializable("ics", a.getSerializable("ics"));
args.putString("status", a.getString("status"));
args.putBoolean("raw", a.getBoolean("raw", false));
@@ -3388,6 +3389,7 @@ public class FragmentCompose extends FragmentBase {
String action = args.getString("action");
long id = args.getLong("id", -1);
long reference = args.getLong("reference", -1);
+ int dsn = args.getInt("dsn", EntityMessage.DSN_RECEIPT);
File ics = (File) args.getSerializable("ics");
String status = args.getString("status");
long answer = args.getLong("answer", -1);
@@ -3609,7 +3611,7 @@ public class FragmentCompose extends FragmentBase {
// References
if ("reply".equals(action) || "reply_all".equals(action) ||
"list".equals(action) ||
- "receipt".equals(action) ||
+ "dsn".equals(action) ||
"participation".equals(action)) {
data.draft.references = (ref.references == null ? "" : ref.references + " ") + ref.msgid;
data.draft.inreplyto = ref.msgid;
@@ -3617,7 +3619,7 @@ public class FragmentCompose extends FragmentBase {
if ("list".equals(action) && ref.list_post != null)
data.draft.to = ref.list_post;
- else if ("receipt".equals(action) && ref.receipt_to != null)
+ else if ("dsn".equals(action) && ref.receipt_to != null)
data.draft.to = ref.receipt_to;
else {
// Prevent replying to self
@@ -3667,8 +3669,8 @@ public class FragmentCompose extends FragmentBase {
if ("reply_all".equals(action))
data.draft.cc = ref.getAllRecipients(data.identities, ref.account);
- else if ("receipt".equals(action)) {
- data.draft.dsn = EntityMessage.DSN_RECEIPT;
+ else if ("dsn".equals(action)) {
+ data.draft.dsn = dsn;
data.draft.receipt_request = false;
}
@@ -3727,10 +3729,17 @@ public class FragmentCompose extends FragmentBase {
}
} else if ("list".equals(action)) {
data.draft.subject = ref.subject;
- } else if ("receipt".equals(action)) {
- data.draft.subject = context.getString(R.string.title_receipt_subject, subject);
+ } else if ("dsn".equals(action)) {
+ if (EntityMessage.DSN_USER_UNKNOWN.equals(dsn))
+ data.draft.subject = context.getString(R.string.title_user_unknown_subject);
+ else
+ data.draft.subject = context.getString(R.string.title_receipt_subject, subject);
- String[] texts = Helper.getStrings(context, ref.language, R.string.title_receipt_text);
+ String[] texts;
+ if (EntityMessage.DSN_USER_UNKNOWN.equals(dsn))
+ texts = new String[]{context.getString(R.string.title_user_unknown_text)};
+ else
+ texts = Helper.getStrings(context, ref.language, R.string.title_receipt_text);
for (int i = 0; i < texts.length; i++) {
if (i > 0)
document.body()
@@ -3782,7 +3791,7 @@ public class FragmentCompose extends FragmentBase {
if (ref.content &&
!"editasnew".equals(action) &&
!("list".equals(action) && TextUtils.isEmpty(s)) &&
- !"receipt".equals(action)) {
+ !"dsn".equals(action)) {
// Reply/forward
Element reply = document.createElement("div");
reply.attr("fairemail", "reference");
diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java
index cfbec0c52d..c6151c8dd8 100644
--- a/app/src/main/java/eu/faircode/email/FragmentMessages.java
+++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java
@@ -2410,6 +2410,10 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
if (data.identities == null)
data.identities = new ArrayList<>();
+ final Context context = getContext();
+ if (context == null)
+ return;
+
final Address[] to =
message.replySelf(data.identities, message.account)
? message.to
@@ -2419,13 +2423,17 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
int answers = args.getInt("answers");
- PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(getContext(), getViewLifecycleOwner(), anchor);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ boolean experiments = prefs.getBoolean("experiments", false);
+
+ PopupMenuLifecycle popupMenu = new PopupMenuLifecycle(context, getViewLifecycleOwner(), anchor);
popupMenu.inflate(R.menu.popup_reply);
popupMenu.getMenu().findItem(R.id.menu_reply_to_all).setVisible(recipients.length > 0);
popupMenu.getMenu().findItem(R.id.menu_reply_list).setVisible(message.list_post != null);
popupMenu.getMenu().findItem(R.id.menu_reply_receipt).setVisible(message.receipt_to != null);
+ popupMenu.getMenu().findItem(R.id.menu_reply_user_unknown).setVisible(experiments);
popupMenu.getMenu().findItem(R.id.menu_new_message).setVisible(to != null && to.length > 0);
- popupMenu.getMenu().findItem(R.id.menu_reply_answer).setVisible(answers != 0 || !ActivityBilling.isPro(getContext()));
+ popupMenu.getMenu().findItem(R.id.menu_reply_answer).setVisible(answers != 0 || !ActivityBilling.isPro(context));
popupMenu.getMenu().findItem(R.id.menu_reply_to_sender).setEnabled(message.content);
popupMenu.getMenu().findItem(R.id.menu_reply_to_all).setEnabled(message.content);
@@ -2446,7 +2454,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
@Override
public boolean onMenuItemClick(MenuItem target) {
if (target.getGroupId() == 1) {
- startActivity(new Intent(getContext(), ActivityCompose.class)
+ startActivity(new Intent(context, ActivityCompose.class)
.putExtra("action", "reply")
.putExtra("reference", message.id)
.putExtra("answer", target.getIntent().getLongExtra("id", -1)));
@@ -2465,7 +2473,10 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
onMenuReply(message, "list", selected);
return true;
case R.id.menu_reply_receipt:
- onMenuReply(message, "receipt");
+ onMenuDsn(message, EntityMessage.DSN_RECEIPT);
+ return true;
+ case R.id.menu_reply_user_unknown:
+ onMenuDsn(message, EntityMessage.DSN_USER_UNKNOWN);
return true;
case R.id.menu_forward:
onMenuReply(message, "forward");
@@ -2506,6 +2517,14 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
startActivity(reply);
}
+ private void onMenuDsn(TupleMessageEx message, int type) {
+ Intent reply = new Intent(getContext(), ActivityCompose.class)
+ .putExtra("action", "dsn")
+ .putExtra("reference", message.id)
+ .putExtra("dsn", type);
+ startActivity(reply);
+ }
+
private void onMenuNew(TupleMessageEx message, Address[] to) {
Intent reply = new Intent(getContext(), ActivityCompose.class)
.putExtra("action", "new")
diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java
index f45d574a9f..4817e6481b 100644
--- a/app/src/main/java/eu/faircode/email/MessageHelper.java
+++ b/app/src/main/java/eu/faircode/email/MessageHelper.java
@@ -573,6 +573,43 @@ public class MessageHelper {
//headersPart.setDisposition(Part.INLINE);
//report.addBodyPart(headersPart);
+ imessage.setContent(report);
+ return;
+ } else if (EntityMessage.DSN_USER_UNKNOWN.equals(message.dsn)) {
+ // https://tools.ietf.org/html/rfc3464
+ Multipart report = new MimeMultipart("report; report-type=delivery-status");
+
+ String html = Helper.readText(message.getFile(context));
+ String plainContent = HtmlHelper.getText(context, html);
+
+ BodyPart plainPart = new MimeBodyPart();
+ plainPart.setContent(plainContent, "text/plain; charset=" + Charset.defaultCharset().name());
+ report.addBodyPart(plainPart);
+
+ String from = null;
+ if (message.from != null && message.from.length > 0)
+ from = ((InternetAddress) message.from[0]).getAddress();
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("Reporting-MTA: dns;").append("dummy.faircode.eu").append("\r\n");
+ sb.append("\r\n");
+
+ if (from != null)
+ sb.append("Final-Recipient: rfc822;").append(from).append("\r\n");
+
+ sb.append("Action: failed").append("\r\n");
+ sb.append("Status: 5.1.1").append("\r\n"); // https://tools.ietf.org/html/rfc3463
+ sb.append("Diagnostic-Code: smtp; 550 user unknown").append("\r\n");
+
+ MailDateFormat mdf = new MailDateFormat();
+ mdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+ sb.append("Last-Attempt-Date: ").append(mdf.format(message.received)).append("\r\n");
+
+ BodyPart dnsPart = new MimeBodyPart();
+ dnsPart.setContent(sb.toString(), "message/delivery-status");
+ dnsPart.setDisposition(Part.INLINE);
+ report.addBodyPart(dnsPart);
+
imessage.setContent(report);
return;
}
diff --git a/app/src/main/res/menu/popup_reply.xml b/app/src/main/res/menu/popup_reply.xml
index 932139f8ac..5075c4c4f1 100644
--- a/app/src/main/res/menu/popup_reply.xml
+++ b/app/src/main/res/menu/popup_reply.xml
@@ -16,6 +16,10 @@
android:id="@+id/menu_reply_receipt"
android:title="@string/title_reply_receipt" />
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b66b4593e7..b9ec6be043 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -887,11 +887,15 @@
Reply to all
Reply to list
Send read receipt
+ Send user unknown
Reply with template
Moving to %1$s (%2$d)
Open with
%1$s authentication failed
+ Delivery Status Notification (Failure)
+ Your message wasn\'t delivered because the address couldn\'t be found.
+
Read receipt: %1$s
This read receipt only acknowledges that the message was displayed. There is no guarantee that the recipient has read the message contents.