Ver Fonte

Merge pull request #71 from Miksus/dev/add_date_header

ENH: Update headers
Mikael Koli há 3 anos atrás
pai
commit
09f2199fe5

+ 32 - 14
ci/test_send.py

@@ -1,4 +1,5 @@
 
+import argparse
 import os, time
 import base64, logging
 from pathlib import Path
@@ -208,20 +209,37 @@ def log_simple():
     logger.info("An info record")
     logger.handlers = []
 
+def main():
+    funcs = {
+        "empty": send_empty,
+        "body_with_text": send_text,
+        "body_with_html": send_html,
+        "body_with_html_and_text": send_test_and_html,
+        "full": send_full_features,
+
+        "images": send_images,
+
+        "with_attachments": send_attachments,
+        "body_with_text_and_attachments": send_attachments_with_text,
+        "body_with_html_and_attachments": send_attachments_with_html,
+
+        "log": log_simple,
+        "log_multi": log_multi,
+    }
+
+    parser = argparse.ArgumentParser(prog="Redmail tester")
+    parser.add_argument(
+        'funcs', type=str, nargs='+',
+        help=f"Test functions to run. Allowed: {list(funcs)}"
+    )
 
-if __name__ == "__main__":
-    fn_bodies = [send_empty, send_text, send_html, send_test_and_html, send_full_features]
-    fn_attachments = [send_attachments, send_attachments_with_text, send_attachments_with_html]
-    fn_log = [log_simple, log_multi]
+    args = parser.parse_args()
 
-    funcs = {
-        "minimal": fn_bodies[0],
-        "bodies": fn_bodies,
-        "full": fn_bodies + fn_attachments + fn_log,
-        "logging": fn_log,
-        "images": fn_imgs,
-    }[os.environ.get("EMAIL_FUNCS", "full")]
-    for func in funcs:
-        print("Running:", func.__name__)
+    for func_name in args.funcs:
+        func = funcs[func_name]
+        func()
         time.sleep(1)
-        func()
+
+
+if __name__ == "__main__":
+    main()

+ 6 - 3
docs/tutorials/testing.rst

@@ -43,7 +43,8 @@ in tests:
     assert str(msg) == """From: me@example.com
     Subject: Some news
     To: you@example.com
-    Message-ID: <167294165062.31860.1664530310632362057@example.com>
+    Message-ID: <167294165062.31860.1664530310632362057@LAPTOP-1234GML0>
+    Date: Dec, 31 Jan 2021 06:56:46 -0000
     Content-Type: text/plain; charset="utf-8"
     Content-Transfer-Encoding: 7bit
     MIME-Version: 1.0
@@ -112,7 +113,8 @@ Then to use this mock:
     assert msgs == ["""From: me@example.com
     Subject: Some news
     To: you@example.com
-    Message-ID: <167294165062.31860.1664530310632362057@example.com>
+    Message-ID: <167294165062.31860.1664530310632362057@LAPTOP-1234GML0>
+    Date: Dec, 31 Jan 2021 06:56:46 -0000
     Content-Type: text/plain; charset="utf-8"
     Content-Transfer-Encoding: 7bit
     MIME-Version: 1.0
@@ -160,7 +162,8 @@ Then to use this class:
     assert msgs == ["""From: me@example.com
     Subject: Some news
     To: you@example.com
-    Message-ID: <167294165062.31860.1664530310632362057@example.com>
+    Message-ID: <167294165062.31860.1664530310632362057@LAPTOP-1234GML0>
+    Date: Dec, 31 Jan 2021 06:56:46 -0000
     Content-Type: text/plain; charset="utf-8"
     Content-Transfer-Encoding: 7bit
     MIME-Version: 1.0

+ 5 - 1
docs/versions.rst

@@ -6,8 +6,12 @@ Version history
 
 - ``0.5.0``
 
-    - Add: Now ``Message-ID`` header is always generated. Sending emails via Gmail may fail without it as of 2022. 
+    - Add: New header, ``Message-ID: ...``. Sending emails via Gmail may fail without it as of 2022. 
+    - Add: New header, ``Date: ...``.
     - Fix: Capitalized email headers including ``From``, ``To`` and ``Subject``.
+    - Update: Content-IDs (used in the embedded images in the HTML body) now uses fully qualified domain name
+      (FQDN) by default. Can be customized by setting ``domain`` attribute in the sender.
+    - Package: Now Red Mail is built using pyproject.toml and CI pipelines were updated.
 
 - ``0.4.2``
 

+ 1 - 1
redmail/email/body.py

@@ -113,7 +113,7 @@ class HTMLBody(Body):
                 Extra Jinja parameters for the HTML.
         """
         if self.use_jinja:
-            domain = parseaddr(msg["from"])[1].split("@")[-1] if self.domain is None else self.domain
+            domain = self.domain
             html, cids = self.render(
                 html, 
                 images=images,

+ 23 - 9
redmail/email/sender.py

@@ -1,7 +1,7 @@
 
 from copy import copy
 from email.message import EmailMessage
-from email.utils import make_msgid
+from email.utils import make_msgid, formatdate
 from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
 import warnings
 
@@ -47,6 +47,11 @@ class EmailSender:
         when connecting to the SMTP server.
     user_name : str, optional
         Deprecated alias for username. Please use username instead.
+    domain : str, optional
+        Portion of the generated IDs after "@" which strengthens the uniqueness
+        of the generated IDs. Used in the Message-ID header and in the Content-IDs 
+        of the embedded imaged in the HTML body. Usually not needed to be set.
+        Defaults to the fully qualified domain name.
     **kwargs : dict
         Additional keyword arguments are passed to initiation in ``cls_smtp``.
         These are stored as attribute ``kws_smtp``
@@ -140,7 +145,15 @@ class EmailSender:
 
     attachment_encoding = 'UTF-8'
 
-    def __init__(self, host:str, port:int, username:str=None, password:str=None, cls_smtp:smtplib.SMTP=smtplib.SMTP, use_starttls:bool=True, **kwargs):
+    def __init__(self,
+                 host:str,
+                 port:int,
+                 username:str=None,
+                 password:str=None,
+                 cls_smtp:smtplib.SMTP=smtplib.SMTP,
+                 use_starttls:bool=True,
+                 domain:Optional[str]=None,
+                 **kwargs):
 
         if "user_name" in kwargs and username is None:
             warnings.warn("Argument user_name was renamed as username. Please use username instead.", FutureWarning)
@@ -164,6 +177,7 @@ class EmailSender:
         self.html_template = None
         self.text_template = None
         self.use_jinja = True
+        self.domain = domain
 
         self.use_starttls = use_starttls
         self.cls_smtp = cls_smtp
@@ -354,7 +368,8 @@ class EmailSender:
                 template=self.get_html_template(html_template),
                 table_template=self.get_html_table_template(),
                 jinja_env=self.templates_html,
-                use_jinja=use_jinja
+                use_jinja=use_jinja,
+                domain=self.domain
             )
             body.attach(
                 msg,
@@ -392,11 +407,8 @@ class EmailSender:
         """Get sender of the email"""
         return sender or self.sender or self.username
 
-    def create_message_id(self, sender:str) -> str:
-        domain = None
-        if sender is not None and '@' in sender:
-            domain = sender.split("@")[1]
-        return make_msgid(domain=domain)
+    def create_message_id(self) -> str:
+        return make_msgid(domain=self.domain)
 
     def _create_body(self, subject, sender, receivers=None, cc=None, bcc=None) -> EmailMessage:
         msg = EmailMessage()
@@ -414,7 +426,9 @@ class EmailSender:
         # Message-IDs could be produced by the first mail server
         # or the program sending the email (as we are doing now).
         # Apparently Gmail might require it as of 2022
-        msg['Message-ID'] = self.create_message_id(sender)
+        msg['Message-ID'] = self.create_message_id()
+
+        msg['Date'] = formatdate()
         return msg
 
     def _set_content_type(self, msg:EmailMessage, has_text, has_html, has_attachments):

+ 36 - 22
redmail/test/email/test_body.py

@@ -8,27 +8,32 @@ from convert import remove_extra_lines, payloads_to_dict
 from getpass import getpass, getuser
 from platform import node
 
-from convert import remove_email_extra, remove_email_content_id, remove_email_message_id
+from convert import remove_email_extra, remove_email_content_id, prune_generated_headers
 
-import platform
-PYTHON_VERSION = platform.sys.version_info
 IS_PY37 = sys.version_info < (3, 8)
 
 def test_text_message():
 
     sender = EmailSender(host=None, port=1234)
+    if IS_PY37:
+        # CI has FQDN that has UTF-8 chars and goes to new line
+        # for Python <=3.7. We set a realistic looking domain
+        # name for easier testing
+        sender.domain = "REDMAIL-1234.mail.com"
+
     msg = sender.get_message(
         sender="me@example.com",
         receivers="you@example.com",
         subject="Some news",
         text="Hi, nice to meet you.",
     )
-    msg = remove_email_message_id(str(msg))
+    msg = prune_generated_headers(str(msg))
     assert str(msg) == dedent("""
     From: me@example.com
     Subject: Some news
     To: you@example.com
     Message-ID: <<message_id>>
+    Date: <date>
     Content-Type: text/plain; charset="utf-8"
     Content-Transfer-Encoding: 7bit
     MIME-Version: 1.0
@@ -40,18 +45,25 @@ def test_text_message():
 def test_html_message():
 
     sender = EmailSender(host=None, port=1234)
+    if IS_PY37:
+        # CI has FQDN that has UTF-8 chars and goes to new line
+        # for Python <=3.7. We set a realistic looking domain
+        # name for easier testing
+        sender.domain = "REDMAIL-1234.mail.com"
+
     msg = sender.get_message(
         sender="me@example.com",
         receivers="you@example.com",
         subject="Some news",
         html="<h3>Hi,</h3><p>Nice to meet you</p>",
     )
-    msg = remove_email_message_id(str(msg))
+    msg = prune_generated_headers(str(msg))
     assert remove_email_content_id(str(msg)) == dedent("""
     From: me@example.com
     Subject: Some news
     To: you@example.com
     Message-ID: <<message_id>>
+    Date: <date>
     Content-Type: multipart/mixed; boundary="===============<ID>=="
 
     --===============<ID>==
@@ -74,6 +86,12 @@ def test_html_message():
 def test_text_and_html_message():
 
     sender = EmailSender(host=None, port=1234)
+    if IS_PY37:
+        # CI has FQDN that has UTF-8 chars and goes to new line
+        # for Python <=3.7. We set a realistic looking domain
+        # name for easier testing
+        sender.domain = "REDMAIL-1234.mail.com"
+
     msg = sender.get_message(
         sender="me@example.com",
         receivers="you@example.com",
@@ -81,12 +99,13 @@ def test_text_and_html_message():
         html="<h3>Hi,</h3><p>nice to meet you.</p>",
         text="Hi, nice to meet you.",
     )
-    msg = remove_email_message_id(str(msg))
+    msg = prune_generated_headers(str(msg))
     assert remove_email_content_id(str(msg)) == dedent("""
     From: me@example.com
     Subject: Some news
     To: you@example.com
     Message-ID: <<message_id>>
+    Date: <date>
     MIME-Version: 1.0
     Content-Type: multipart/mixed; boundary="===============<ID>=="
 
@@ -180,6 +199,12 @@ def test_without_jinja(use_jinja_obj, use_jinja):
     text = "Hi, \nThis is {{ user }} from { node }. I'm really {{ sender.full_name }}."
 
     sender = EmailSender(host=None, port=1234)
+    if IS_PY37:
+        # CI has FQDN that has UTF-8 chars and goes to new line
+        # for Python <=3.7. We set a realistic looking domain
+        # name for easier testing
+        sender.domain = "REDMAIL-1234.mail.com"
+
     sender.use_jinja = use_jinja_obj
     msg = sender.get_message(
         sender="me@example.com",
@@ -195,6 +220,7 @@ def test_without_jinja(use_jinja_obj, use_jinja):
     Subject: Some news
     To: you@example.com
     Message-ID: <<message_id>>
+    Date: <date>
     MIME-Version: 1.0
     Content-Type: multipart/mixed; boundary="===============<ID>=="
 
@@ -223,7 +249,7 @@ def test_without_jinja(use_jinja_obj, use_jinja):
     """)[1:]
     if IS_PY37:
         expected = expected.replace('sender.full_n=\n', 'sender.full_n')
-    msg = remove_email_message_id(str(msg))
+    msg = prune_generated_headers(str(msg))
     assert remove_email_content_id(msg) == expected
 
 
@@ -262,7 +288,7 @@ def test_set_defaults():
     email.subject = "Some email"
     msg = email.get_message(text="Hi, an email")
     headers = {
-        key: val if key not in ('Message-ID',) else '<ID>'
+        key: val if key not in ('Message-ID', 'Date') else '<ID>'
         for key, val in msg.items()
     }
     assert {
@@ -273,21 +299,9 @@ def test_set_defaults():
         'Content-Transfer-Encoding': '7bit', 
         'MIME-Version': '1.0',
         'Message-ID': '<ID>',
+        'Date': '<ID>',
     } == headers
 
-def test_cc_bcc():
-    email = EmailSender(host=None, port=1234)
-    msg = email.get_message(sender="me@example.com", subject="Some email", cc=['you@example.com'], bcc=['he@example.com', 'she@example.com'])
-    msg = remove_email_message_id(str(msg))
-    assert remove_email_content_id(msg) == dedent("""
-    From: me@example.com
-    Subject: Some email
-    Cc: you@example.com
-    Bcc: he@example.com, she@example.com
-    Message-ID: <<message_id>>
-
-    """)[1:]
-
 def test_missing_subject():
     email = EmailSender(host=None, port=1234)
     with pytest.raises(ValueError):
@@ -312,7 +326,7 @@ def test_no_table_templates():
     headers = {
         key: val
         for key, val in msg.items()
-        if key not in ('Message-ID',)
+        if key not in ('Message-ID', 'Date')
     }
     assert headers == {
         'From': 'me@gmail.com', 

+ 11 - 4
redmail/test/email/test_cookbook.py

@@ -1,9 +1,10 @@
-
-from typing import Union
+import sys
 from textwrap import dedent
 from redmail import EmailSender
 
-from convert import remove_email_content_id, remove_email_message_id
+from convert import remove_email_content_id, prune_generated_headers
+
+IS_PY37 = sys.version_info < (3, 8)
 
 def test_distributions():
     class DistrSender(EmailSender):
@@ -33,6 +34,11 @@ def test_distributions():
             'group2': ["he@example.com", "she@example.com"],
         }
     )
+    if IS_PY37:
+        # CI has FQDN that has UTF-8 chars and goes to new line
+        # for Python <=3.7. We set a realistic looking domain
+        # name for easier testing
+        email.domain = "REDMAIL-1234.mail.com"
 
     msg = email.get_message(
         sender="me@example.com",
@@ -40,7 +46,7 @@ def test_distributions():
         cc="group2",
         subject="Some email",
     )
-    msg = remove_email_message_id(str(msg))
+    msg = prune_generated_headers(str(msg))
     msg = remove_email_content_id(str(msg))
     assert msg == dedent("""
     From: me@example.com
@@ -48,5 +54,6 @@ def test_distributions():
     To: me@example.com, you@example.com
     Cc: he@example.com, she@example.com
     Message-ID: <<message_id>>
+    Date: <date>
     
     """)[1:]

+ 74 - 0
redmail/test/email/test_headers.py

@@ -0,0 +1,74 @@
+import datetime
+import socket
+from textwrap import dedent
+import sys
+import re
+
+from redmail import EmailSender
+
+from convert import remove_email_content_id, prune_generated_headers
+
+import platform
+PYTHON_VERSION = sys.version_info
+IS_PY37 = sys.version_info < (3, 8)
+
+def test_date():
+    format = "%a, %d %b %Y %H:%M:%S -0000"
+    email = EmailSender(host=None, port=1234)
+
+    before = datetime.datetime.now(datetime.timezone.utc)
+    msg = email.get_message(sender="me@example.com", subject="Some email")
+    after = datetime.datetime.now(datetime.timezone.utc)
+    date_strings = re.findall(r'(?<=Date: ).+', str(msg))
+    assert len(date_strings) == 1
+    for dt_string in date_strings:
+    
+        # Validate the Date fits to the format
+        datetime.datetime.strptime(dt_string, format)
+
+        # It should not take longer than second to generate the email
+        assert dt_string in (before.strftime(format), after.strftime(format))
+
+def test_message_id():
+    domain = socket.getfqdn()
+    email = EmailSender(host=None, port=1234)
+
+    if IS_PY37:
+        # Python <=3.7 has problems with domain names with UTF-8
+        # This is mostly problem with CI.
+        # We simulate realistic domain name
+        domain = "REDMAIL-1234.mail.com"
+        email.domain = domain
+
+    msg = email.get_message(sender="me@example.com", subject="Some email")
+    msg2 = email.get_message(sender="me@example.com", subject="Some email")
+
+    message_ids = re.findall(r'(?<=Message-ID: ).+', str(msg))
+    assert len(message_ids) == 1
+    message_id = message_ids[0]
+
+    # [0-9]{{12}}[.][0-9]{{5}}[.][0-9]{{20}}
+    assert bool(re.search(fr'<[0-9.]+@{domain}>', message_id))
+
+    # Check another email has not the same Message-ID
+    message_id_2 = re.findall(r'(?<=Message-ID: ).+', str(msg2))[0]
+    assert message_id != message_id_2
+
+def test_cc_bcc():
+    email = EmailSender(host=None, port=1234)
+    if IS_PY37:
+        # CI has FQDN that has UTF-8 chars and goes to new line
+        # for Python <=3.7. We set a realistic looking domain
+        # name for easier testing
+        email.domain = "REDMAIL-1234.mail.com"
+    msg = email.get_message(sender="me@example.com", subject="Some email", cc=['you@example.com'], bcc=['he@example.com', 'she@example.com'])
+    msg = prune_generated_headers(str(msg))
+    assert remove_email_content_id(msg) == dedent("""
+    From: me@example.com
+    Subject: Some email
+    Cc: you@example.com
+    Bcc: he@example.com, she@example.com
+    Message-ID: <<message_id>>
+    Date: <date>
+
+    """)[1:]

+ 3 - 3
redmail/test/email/test_inline_media.py

@@ -65,7 +65,7 @@ def test_with_image_file(get_image_obj, dummy_png):
     headers = {
         key: val
         for key, val in msg.items()
-        if key not in ('Message-ID',)
+        if key not in ('Message-ID', 'Date')
     }
     assert {
         'From': 'me@gmail.com', 
@@ -117,7 +117,7 @@ def test_with_image_dict_jpeg():
     headers = {
         key: val
         for key, val in msg.items()
-        if key not in ('Message-ID',)
+        if key not in ('Message-ID', 'Date')
     }
     assert {
         'From': 'me@gmail.com', 
@@ -159,7 +159,7 @@ def test_with_image_obj(get_image_obj):
     headers = {
         key: val
         for key, val in msg.items()
-        if key not in ('Message-ID',)
+        if key not in ('Message-ID', 'Date')
     }
     assert {
         'From': 'me@gmail.com', 

+ 22 - 3
redmail/test/email/test_structure.py

@@ -1,3 +1,4 @@
+import socket
 from redmail import EmailSender
 
 import pytest
@@ -90,6 +91,12 @@ def test_html_inline():
     img_bytes = base64.b64decode(img_data)
 
     sender = EmailSender(host=None, port=1234)
+
+    # CI may have long domain name thus we set it to such
+    # that's shorter but looks realistic
+    host = "REDMAIL-1234.mail.com"
+    sender.domain = host
+
     msg = sender.get_message(
         sender="me@example.com",
         receivers="you@example.com",
@@ -109,7 +116,7 @@ def test_html_inline():
         "multipart/mixed": {
             "multipart/alternative": {
                 "multipart/related": {
-                    'text/html': f'<p>HTML content</p> \n<img src="cid:{cid}@example.com">\n',
+                    'text/html': f'<p>HTML content</p> \n<img src="cid:{cid}@{host}">\n',
                     'image/jpg': '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcG\nBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwM\nDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAABAAEDASIA\nAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA\nAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3\nODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm\np6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA\nAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx\nBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK\nU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3\nuLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD5rooo\nr8DP9oD/2Q==\n',
                 }
             },
@@ -121,6 +128,12 @@ def test_text_html_inline_attachment():
     img_bytes = base64.b64decode(img_data)
 
     sender = EmailSender(host=None, port=1234)
+
+    # CI may have long domain name thus we set it to such
+    # that's shorter but looks realistic
+    host = "REDMAIL-1234.mail.com"
+    sender.domain = host
+
     msg = sender.get_message(
         sender="me@example.com",
         receivers="you@example.com",
@@ -143,7 +156,7 @@ def test_text_html_inline_attachment():
             "multipart/alternative": {
                 'text/plain': 'Text content\n',
                 "multipart/related": {
-                    'text/html': f'<p>HTML content</p> \n<img src="cid:{cid}@example.com">\n',
+                    'text/html': f'<p>HTML content</p> \n<img src="cid:{cid}@{host}">\n',
                     'image/jpg': '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcG\nBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwM\nDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAABAAEDASIA\nAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA\nAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3\nODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm\np6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA\nAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx\nBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK\nU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3\nuLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD5rooo\nr8DP9oD/2Q==\n',
                 }
             },
@@ -157,6 +170,12 @@ def test_text_html_inline_attachment_multiple():
     img_bytes = base64.b64decode(img_data)
 
     sender = EmailSender(host=None, port=1234)
+
+    # CI may have long domain name thus we set it to such
+    # that's shorter but looks realistic
+    host = "REDMAIL-1234.mail.com"
+    sender.domain = host
+
     msg = sender.get_message(
         sender="me@example.com",
         receivers="you@example.com",
@@ -186,7 +205,7 @@ def test_text_html_inline_attachment_multiple():
             "multipart/alternative": {
                 'text/plain': 'Text content\n',
                 "multipart/related": {
-                    'text/html': f'<p>HTML content</p> \n<img src="cid:{cid_1}@example.com">\n<img src="cid:{cid_2}@example.com">\n',
+                    'text/html': f'<p>HTML content</p> \n<img src="cid:{cid_1}@{host}">\n<img src="cid:{cid_2}@{host}">\n',
                     'image/jpg':   '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcG\nBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwM\nDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAABAAEDASIA\nAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA\nAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3\nODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm\np6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA\nAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx\nBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK\nU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3\nuLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD5rooo\nr8DP9oD/2Q==\n',
                     'image/jpg_1': '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcG\nBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwM\nDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAABAAEDASIA\nAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA\nAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3\nODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm\np6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA\nAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx\nBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK\nU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3\nuLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD5rooo\nr8DP9oD/2Q==\n',
                 }

+ 11 - 4
redmail/test/email/test_template.py

@@ -1,13 +1,13 @@
+import sys
 from textwrap import dedent
 from jinja2 import Environment
 from redmail import EmailSender
 
 import pytest
 
-from convert import remove_email_extra, remove_email_content_id, remove_email_message_id
-from getpass import getpass, getuser
-from platform import node
+from convert import remove_email_extra, remove_email_content_id, prune_generated_headers
 
+IS_PY37 = sys.version_info < (3, 8)
 
 def test_template(tmpdir):
     
@@ -54,6 +54,12 @@ def test_template(tmpdir):
 def test_jinja_env(tmpdir):
 
     sender = EmailSender(host=None, port=1234)
+    if IS_PY37:
+        # CI has FQDN that has UTF-8 chars and goes to new line
+        # for Python <=3.7. We set a realistic looking domain
+        # name for easier testing
+        sender.domain = "REDMAIL-1234.mail.com"
+
     env = Environment()
     env.globals["my_param"] = "<a value>"
     sender.templates_text = env
@@ -66,13 +72,14 @@ def test_jinja_env(tmpdir):
         html="<h1>A param: {{ my_param }}</h1>"
     )
     content = str(msg)
-    content = remove_email_message_id(content)
+    content = prune_generated_headers(content)
     content = remove_email_content_id(content)
     assert content == dedent("""
     From: me@example.com
     Subject: Some news
     To: you@example.com
     Message-ID: <<message_id>>
+    Date: <date>
     MIME-Version: 1.0
     Content-Type: multipart/mixed; boundary="===============<ID>=="
 

+ 14 - 0
redmail/test/helpers/convert.py

@@ -16,6 +16,20 @@ def remove_email_content_id(s:str, repl="<ID>"):
 def remove_email_message_id(s:str, repl="<message_id>"):
     return re.sub(r"(?<=Message-ID: <).+?(?=>)", repl, s)
 
+def remove_date(s:str, repl="<date>"):
+    regex = r'(?<=Date: )[A-Za-z]{3}, [0-9]{2} [A-Za-z]{3} [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2}( [+-][0-9]{4})?'
+    return re.sub(regex, repl, s)
+
+def prune_generated_headers(s:str):
+    transformers = (
+        remove_email_content_id,
+        remove_email_message_id,
+        remove_date
+    )
+    for transf in transformers:
+        s = transf(s)
+    return s
+
 def payloads_to_dict(*parts):
     data = {}
     for part in parts:

+ 1 - 1
redmail/test/log/test_handler.py

@@ -170,7 +170,7 @@ def test_emit(logger, kwargs, exp_headers, exp_payload):
     headers = {
         key: val
         for key, val in msg.items()
-        if key not in ('Message-ID',)
+        if key not in ('Message-ID', 'Date')
     }
     payload = msg.get_payload()
 

+ 3 - 3
redmail/test/log/test_handler_multi.py

@@ -180,7 +180,7 @@ def test_emit(logger, kwargs, exp_headers, exp_payload):
     headers = {
         key: val
         for key, val in msg.items()
-        if key not in ('Message-ID',)
+        if key not in ('Message-ID', 'Date')
     }
     payload = msg.get_payload()
 
@@ -213,7 +213,7 @@ def test_flush_multiple(logger):
     headers = {
         key: val
         for key, val in msg.items()
-        if key not in ('Message-ID',)
+        if key not in ('Message-ID', 'Date')
     }
     text = msg.get_payload()
 
@@ -249,7 +249,7 @@ def test_flush_none():
     headers = {
         key: val
         for key, val in msg.items()
-        if key not in ('Message-ID',)
+        if key not in ('Message-ID', 'Date')
     }
     text = msg.get_payload()