owenrusk.dev

errands

small jobs the runtime does for the studio.

git clone https://owenrusk.dev/errands.git

commit 8b3743220846bdc4d9f6b0ffcdbf9e72945a4ecf
parent ba320ecb39c81b07c9a26a897ec87d5706fd241c
author Owen Rusk <owen@papermothgames.com>
date   2026-09-16 15:30:52 -0500
mail: keep a copy of everything sent

tess wants to read what goes out.
errands/mail.py+12-0
tests/test_mail.py+20-1
diff --git a/errands/mail.py b/errands/mail.py
index f9e88ab..5e99730 100644
--- a/errands/mail.py
+++ b/errands/mail.py
@@ -1,6 +1,7 @@
 # the support inbox. imap to read, smtp to send.
 import email
 import imaplib
+import mailbox
 import smtplib
 from dataclasses import dataclass
 from email.header import decode_header, make_header
@@ -97,9 +98,20 @@ def send(msg: EmailMessage, open_server=smtp) -> str:
         server.send_message(msg)
     finally:
         server.quit()
+    keep(msg)
     return msg["Message-ID"]
 
 
+def keep(msg: EmailMessage) -> None:
+    settings.LOGS.mkdir(parents=True, exist_ok=True)
+    box = mailbox.mbox(settings.LOGS / "sent.mbox")
+    try:
+        box.add(msg)
+        box.flush()
+    finally:
+        box.close()
+
+
 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 0b13037..156a91e 100644
--- a/tests/test_mail.py
+++ b/tests/test_mail.py
@@ -1,7 +1,10 @@
+import mailbox
+import tempfile
 import unittest
 from email.message import EmailMessage
+from pathlib import Path
 
-from errands import mail
+from errands import mail, settings
 from tests.fakes import FakeBox, FakeServer, raw
 
 
@@ -43,6 +46,22 @@ class UnreadTest(unittest.TestCase):
 
 
 class SendTest(unittest.TestCase):
+    def setUp(self):
+        self.dir = tempfile.TemporaryDirectory()
+        self.logs = settings.LOGS
+        settings.LOGS = Path(self.dir.name)
+
+    def tearDown(self):
+        settings.LOGS = self.logs
+        self.dir.cleanup()
+
+    def test_keeps_a_copy(self):
+        mail.send(mail.compose("someone@example.com", "hello", "hello\n"), FakeServer)
+        mail.send(mail.compose("ann@example.com", "again", "hello\n"), FakeServer)
+        kept = mailbox.mbox(settings.LOGS / "sent.mbox")
+        self.assertEqual([m["To"] for m in kept], ["someone@example.com", "ann@example.com"])
+        kept.close()
+
     def test_reply_keeps_the_subject_and_thread(self):
         original = mail.parse("7", raw(subject="a question", message_id="<q@example.com>"))
         server = FakeServer()