Feedback reply: Reply-To header routes reader replies to our inbox

Per Codex: outgoing reply now sets Reply-To = GOODNEWS_REPLY_TO_EMAIL, falling
back to the From address. Never the reader's own address (they're the recipient).
send_email gained an optional reply_to param. Failed-send stays UI-only (draft
kept) — no schema change, per Codex's lean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-09 13:24:34 -04:00
parent 6bfee767d0
commit 9ba9851f6d
2 changed files with 57 additions and 2 deletions
+9 -2
View File
@@ -25,10 +25,12 @@ def _cfg() -> dict:
"user": os.environ.get("GOODNEWS_SMTP_USER", ""),
"password": os.environ.get("GOODNEWS_SMTP_PASSWORD", ""),
"sender": os.environ.get("GOODNEWS_SMTP_FROM", "Upbeat Bytes <hello@upbeatbytes.com>"),
# Where a reader's reply should land; falls back to the From address.
"reply_to": os.environ.get("GOODNEWS_REPLY_TO_EMAIL", ""),
}
def send_email(to: str, subject: str, text: str, html: str | None = None) -> None:
def send_email(to: str, subject: str, text: str, html: str | None = None, reply_to: str | None = None) -> None:
"""Send one message. Raises on failure (caller decides how loud to be)."""
cfg = _cfg()
if not cfg["host"]:
@@ -36,6 +38,8 @@ def send_email(to: str, subject: str, text: str, html: str | None = None) -> Non
msg = EmailMessage()
msg["From"] = cfg["sender"]
msg["To"] = to
if reply_to:
msg["Reply-To"] = reply_to
msg["Subject"] = subject
msg.set_content(text)
if html:
@@ -87,7 +91,10 @@ def send_feedback_reply(to: str, reply_text: str, reply_html: str | None, origin
f'<blockquote style="color:#5d6b78;border-left:3px solid #e8e3d8;margin:0;padding-left:12px">{oq}</blockquote>'
"<p>Thanks for reaching out.<br>— Upbeat Bytes</p></div>"
)
send_email(to, subject, text, html=body_html)
# Route the reader's reply to our chosen inbox (never back to the reader).
cfg = _cfg()
reply_to = cfg["reply_to"] or cfg["sender"]
send_email(to, subject, text, html=body_html, reply_to=reply_to)
def send_magic_link(to: str, link: str) -> None:
+48
View File
@@ -0,0 +1,48 @@
import goodnews.email_send as es
class _FakeSMTP:
last = {}
def __init__(self, *a, **k):
pass
def __enter__(self):
return self
def __exit__(self, *a):
return False
def ehlo(self):
pass
def starttls(self, context=None):
pass
def login(self, *a):
pass
def send_message(self, msg):
_FakeSMTP.last = {"To": msg["To"], "Reply-To": msg["Reply-To"], "From": msg["From"]}
def _arm(monkeypatch):
monkeypatch.setenv("GOODNEWS_SMTP_HOST", "smtp.example.com")
monkeypatch.setattr(es.smtplib, "SMTP", _FakeSMTP)
def test_reply_to_uses_env_inbox(monkeypatch):
_arm(monkeypatch)
monkeypatch.setenv("GOODNEWS_REPLY_TO_EMAIL", "inbox@upbeatbytes.com")
es.send_feedback_reply("reader@x.com", "hello", "<p>hello</p>", "original?")
assert _FakeSMTP.last["Reply-To"] == "inbox@upbeatbytes.com"
assert _FakeSMTP.last["To"] == "reader@x.com" # reader is recipient
assert _FakeSMTP.last["Reply-To"] != "reader@x.com" # never the reader
def test_reply_to_falls_back_to_from(monkeypatch):
_arm(monkeypatch)
monkeypatch.delenv("GOODNEWS_REPLY_TO_EMAIL", raising=False)
monkeypatch.setenv("GOODNEWS_SMTP_FROM", "Upbeat Bytes <hello@upbeatbytes.com>")
es.send_feedback_reply("reader@x.com", "hi", None, "o")
assert _FakeSMTP.last["Reply-To"] == "Upbeat Bytes <hello@upbeatbytes.com>"