Skip to content

Commit 6c0c328

Browse files
committed
Serialize Message.as_bytes() with CRLF line endings so SMTP delivery sends RFC 5321-compliant DATA payloads.
1 parent 3a5a55c commit 6c0c328

5 files changed

Lines changed: 11 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
### Fixed
1111

1212
- Jinja2 is now an optional dependency — install with `pip install emails[jinja2]` (#207, #161)
13+
- `Message.as_bytes()` now serializes messages with CRLF line endings for RFC 5321-compliant SMTP delivery (#213)
1314

1415
## 1.0
1516

docs/api.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ Message Methods
156156

157157
.. method:: Message.as_bytes(message_cls=None)
158158

159-
Return the message as bytes, including DKIM signature if configured.
159+
Return the message as bytes with CRLF line endings, including DKIM
160+
signature if configured.
160161

161162
:param message_cls: Optional custom MIME message class.
162163
:returns: Message as bytes.

emails/message.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
load_email_charsets() # sic!
2121

22+
RFC5321_LINESEP = '\r\n'
23+
2224

2325
# Type alias for email addresses accepted by the public API
2426
_Address = str | tuple[str | None, str] | None
@@ -366,7 +368,7 @@ def as_bytes(self, message_cls: type | None = None) -> bytes:
366368
"""
367369
Returns message as bytes.
368370
"""
369-
r = self.build_message(message_cls=message_cls).as_bytes()
371+
r = self.build_message(message_cls=message_cls).as_bytes(linesep=RFC5321_LINESEP)
370372
if self._signer:
371373
r = self.sign_bytes(r)
372374
return r

emails/testsuite/message/test_dkim.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ def test_dkim_as_bytes(dkim_keys):
114114
result = message.as_bytes()
115115
assert isinstance(result, bytes)
116116
assert b'DKIM-Signature: ' in result
117+
assert b'\r\n' in result
118+
assert b'\n' not in result.replace(b'\r\n', b'')
117119

118120

119121
def test_dkim_sign_after_error(dkim_keys):

emails/testsuite/message/test_send_async.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ async def test_send_async_with_dict(mock_smtp):
4040
response = await msg.send_async(smtp={'host': 'localhost', 'port': 2525})
4141
assert response is not None
4242
assert response.success
43+
sent_message = mock_smtp.data.await_args.args[0]
44+
assert b'\r\n' in sent_message
45+
assert b'\n' not in sent_message.replace(b'\r\n', b'')
4346
# Backend should have been closed (quit called)
4447
mock_smtp.quit.assert_awaited_once()
4548

0 commit comments

Comments
 (0)