errands
small jobs the runtime does for the studio.
git clone https://owenrusk.dev/errands.git
commit c708695ab10718280a5615c6df88a70c05a5be70 parent ebf370e163bf3f7792d33acb76676259589504b8 author Owen Rusk <owen@papermothgames.com> date 2024-10-22 16:48:05 -0500
mail: reply and send
| errands/mail.py | +38 | -2 |
| tests/test_mail.py | +31 | -0 |
diff --git a/errands/mail.py b/errands/mail.py index 7d77a2b..5cfbc6c 100644 --- a/errands/mail.py +++ b/errands/mail.py @@ -1,8 +1,10 @@-# the support inbox. imap to read.+# the support inbox. imap to read, smtp to send. import email import imaplib +import smtplib from dataclasses import dataclass-from email.utils import parseaddr+from email.message import EmailMessage +from email.utils import formatdate, make_msgid, parseaddr from . import settings @@ -54,3 +56,37 @@ def mark_read(uid: str, open_box=connect) -> None: box.uid("store", uid, "+FLAGS", "(\\Seen)") finally: box.logout() + + +def compose(to: str, subject: str, body: str, reply_to: Message | None = None) -> EmailMessage: + msg = EmailMessage() + msg["From"] = f"{settings.SUPPORT_NAME} <{settings.SUPPORT_ADDRESS}>" + msg["To"] = to + msg["Subject"] = subject + msg["Date"] = formatdate(localtime=True) + msg["Message-ID"] = make_msgid(domain="papermothgames.com") + if reply_to is not None: + msg["In-Reply-To"] = reply_to.message_id + msg.set_content(body) + return msg + + +def smtp() -> smtplib.SMTP: + server = smtplib.SMTP(settings.env("ERRANDS_SMTP_HOST"), 587) + server.starttls() + server.login(settings.env("ERRANDS_MAIL_USER"), settings.env("ERRANDS_MAIL_PASSWORD")) + return server + + +def send(msg: EmailMessage, open_server=smtp) -> str: + server = open_server() + try: + server.send_message(msg) + finally: + server.quit() + return msg["Message-ID"] + + +def reply(original: Message, body: str, open_server=smtp) -> str: + subject = original.subject if original.subject.lower().startswith("re:") else f"Re: {original.subject}" + return send(compose(original.sender, subject, body, reply_to=original), open_server) diff --git a/tests/test_mail.py b/tests/test_mail.py index 00e343e..f1813f0 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -51,3 +51,34 @@ class UnreadTest(unittest.TestCase): box = FakeBox({}) mail.mark_read("7", lambda: box) self.assertEqual(box.stored, [("7", "+FLAGS", "(\\Seen)")]) + + +class FakeServer: + def __init__(self): + self.sent = [] + self.quit_called = False + + def send_message(self, msg): + self.sent.append(msg) + + def quit(self): + self.quit_called = True + + +class SendTest(unittest.TestCase): + def test_reply_keeps_the_subject_and_thread(self): + original = mail.parse("7", raw(subject="a question", message_id="<q@example.com>")) + server = FakeServer() + mail.reply(original, "thanks for writing.\n", lambda: server) + sent = server.sent[0] + self.assertEqual(sent["To"], "someone@example.com") + self.assertEqual(sent["Subject"], "Re: a question") + self.assertEqual(sent["In-Reply-To"], "<q@example.com>") + self.assertEqual(sent["From"], "Paper Moth Games <support@papermothgames.com>") + self.assertTrue(server.quit_called) + + def test_no_second_re(self): + original = mail.parse("7", raw(subject="Re: a question")) + server = FakeServer() + mail.reply(original, "ok\n", lambda: server) + self.assertEqual(server.sent[0]["Subject"], "Re: a question")