Skip to content

Commit bbe9ffd

Browse files
authored
JAMES-5431 add SanitizeMimeMessageId mailet (#2718)
1 parent 63b7ab0 commit bbe9ffd

File tree

4 files changed

+155
-1
lines changed

4 files changed

+155
-1
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
=== SanitizeMimeMessageId Mailet
2+
3+
Some email clients, such as Outlook for Android, may send emails **without a `Message-ID` header**.
4+
5+
== Why is this needed?
6+
The absence of the `Message-ID` header can cause emails to be rejected by downstream mail servers,
7+
as required by RFC 5322 specifications. For example, Gmail rejects messages without this header with
8+
an error like the following:
9+
550-5.7.1 [IP] Messages missing a valid Message-ID header are not
10+
550-5.7.1 accepted. For more information, go to
11+
550-5.7.1 https://support.google.com/mail/?p=RfcMessageNonCompliant and review
12+
550 5.7.1 RFC 5322 specifications.
13+
14+
To mitigate this, the `SanitizeMimeMessageId` mailet will automatically add a `Message-ID` to emails **that lack one**.
15+
16+
== Configuration
17+
18+
You can configure it simply by adding the following in your `mailetcontainer.xml` file:
19+
20+
....
21+
<mailet match="All" class="SanitizeMimeMessageId"/>
22+
....
23+
24+
== Behavior
25+
26+
- If a `Message-ID` header already exists: nothing is done.
27+
- If the header is missing: a new one is generated and added.
28+

docs/modules/servers/partials/configure/mailets.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,6 @@ include::partial$UseHeaderRecipients.adoc[]
147147

148148
include::partial$WrapText.adoc[]
149149

150-
include::partial$SubAddressing.adoc[]
150+
include::partial$SubAddressing.adoc[]
151+
152+
include::partial$SanitizeMimeMessageId.adoc[]
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/****************************************************************
2+
* Licensed to the Apache Software Foundation (ASF) under one *
3+
* or more contributor license agreements. See the NOTICE file *
4+
* distributed with this work for additional information *
5+
* regarding copyright ownership. The ASF licenses this file *
6+
* to you under the Apache License, Version 2.0 (the *
7+
* "License"); you may not use this file except in compliance *
8+
* with the License. You may obtain a copy of the License at *
9+
* *
10+
* http://www.apache.org/licenses/LICENSE-2.0 *
11+
* *
12+
* Unless required by applicable law or agreed to in writing, *
13+
* software distributed under the License is distributed on an *
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15+
* KIND, either express or implied. See the License for the *
16+
* specific language governing permissions and limitations *
17+
* under the License. *
18+
****************************************************************/
19+
20+
package org.apache.james.transport.mailets;
21+
22+
import jakarta.mail.MessagingException;
23+
24+
import org.apache.mailet.Mail;
25+
import org.apache.mailet.base.GenericMailet;
26+
27+
/**
28+
* The `SanitizeMimeMessageId` mailet is designed to address a specific issue where some email clients, such as Outlook for Android, do not add the MIME `Message-ID` header to the emails they send.
29+
* The absence of the `Message-ID` header can cause emails to be rejected by downstream mail servers,
30+
* as required by RFC 5322 specifications.
31+
*
32+
* Sample configuration:
33+
*
34+
* <pre><code>
35+
* &lt;mailet match="All" class="SanitizeMimeMessageId"&gt;
36+
* &lt;/mailet&gt;
37+
* </code></pre>
38+
*/
39+
public class SanitizeMimeMessageId extends GenericMailet {
40+
41+
@Override
42+
public void service(Mail mail) throws MessagingException {
43+
if (mail.getMessage().getMessageID() == null) {
44+
mail.getMessage().saveChanges();
45+
}
46+
}
47+
48+
@Override
49+
public String getMailetInfo() {
50+
return "SanitizeMimeMessageId Mailet";
51+
}
52+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/****************************************************************
2+
* Licensed to the Apache Software Foundation (ASF) under one *
3+
* or more contributor license agreements. See the NOTICE file *
4+
* distributed with this work for additional information *
5+
* regarding copyright ownership. The ASF licenses this file *
6+
* to you under the Apache License, Version 2.0 (the *
7+
* "License"); you may not use this file except in compliance *
8+
* with the License. You may obtain a copy of the License at *
9+
* *
10+
* http://www.apache.org/licenses/LICENSE-2.0 *
11+
* *
12+
* Unless required by applicable law or agreed to in writing, *
13+
* software distributed under the License is distributed on an *
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15+
* KIND, either express or implied. See the License for the *
16+
* specific language governing permissions and limitations *
17+
* under the License. *
18+
****************************************************************/
19+
package org.apache.james.transport.mailets;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
23+
import java.util.Properties;
24+
25+
import jakarta.mail.Session;
26+
import jakarta.mail.internet.MimeMessage;
27+
28+
import org.apache.mailet.base.test.FakeMail;
29+
import org.apache.mailet.base.test.FakeMailetConfig;
30+
import org.junit.jupiter.api.BeforeEach;
31+
import org.junit.jupiter.api.Test;
32+
33+
public class SanitizeMimeMessageIdTest {
34+
35+
private SanitizeMimeMessageId testee;
36+
37+
@BeforeEach
38+
public void setUp() throws Exception {
39+
testee = new SanitizeMimeMessageId();
40+
testee.init(FakeMailetConfig.builder()
41+
.mailetName("SanitizeMimeMessageId")
42+
.build());
43+
}
44+
45+
@Test
46+
public void shouldAddMessageIdWhenMissing() throws Exception {
47+
MimeMessage mimeMessage = new MimeMessage(Session.getDefaultInstance(new Properties()));
48+
FakeMail mail = FakeMail.builder()
49+
.name("mail")
50+
.mimeMessage(mimeMessage)
51+
.build();
52+
53+
testee.service(mail);
54+
55+
assertThat(mail.getMessage().getHeader("Message-ID")).isNotNull();
56+
}
57+
58+
@Test
59+
public void shouldNotModifyExistingMessageId() throws Exception {
60+
MimeMessage mimeMessage = new MimeMessage(Session.getDefaultInstance(new Properties()));
61+
mimeMessage.setHeader("Message-ID", "<[email protected]>");
62+
FakeMail mail = FakeMail.builder()
63+
.name("mail")
64+
.mimeMessage(mimeMessage)
65+
.build();
66+
67+
testee.service(mail);
68+
69+
assertThat(mail.getMessage().getHeader("Message-ID")).containsExactly("<[email protected]>");
70+
}
71+
}
72+

0 commit comments

Comments
 (0)