MAS-WP-0003-T06: lifetime cap on sent replies (300), discard Postfix bounces

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 252470@bnt-lap001
Assistant-Session: ad628c8f-4be2-4979-94c9-76c7ed6f8241
This commit is contained in:
tegwick 2026-10-18 13:32:32 +02:00
parent 8559b70dbd
commit d4585eb542
5 changed files with 54 additions and 5 deletions

View file

@ -56,7 +56,8 @@ CREATE TABLE IF NOT EXISTS outbox (
class TicketService:
def __init__(self, db_path: str = ":memory:", id_factory: Callable[[], str] = default_ticket_id,
smtp: Optional[tuple[str, int]] = None, closed_notice: bool = True):
smtp: Optional[tuple[str, int]] = None, closed_notice: bool = True,
max_sent: Optional[int] = None):
self.db = sqlite3.connect(db_path, check_same_thread=False)
self.db.row_factory = sqlite3.Row
self.db.executescript(SCHEMA)
@ -64,6 +65,7 @@ class TicketService:
self.new_id = id_factory
self.smtp = smtp
self.closed_notice = closed_notice
self.max_sent = max_sent # lifetime cap on mail actually sent via smtp
# -- event handling -------------------------------------------------------------
@ -141,10 +143,18 @@ class TicketService:
subject = "Re: " + subject
self.db.execute("INSERT INTO outbox (ticket_id, mail_from, mail_to, subject, body, in_reply_to)"
" VALUES (?,?,?,?,?,?)", (tid, mail_from, to, subject, body, msg.get("message_id")))
if self.smtp:
seq = self.db.execute("SELECT last_insert_rowid()").fetchone()[0]
if self.smtp and not self.send_cap_reached():
self._send(mail_from, to, subject, body, msg.get("message_id"))
self.db.execute("UPDATE outbox SET sent=1 WHERE seq=?", (seq,))
return True
def send_cap_reached(self) -> bool:
"""Over the cap, replies stay in the outbox unsent."""
if self.max_sent is None:
return False
return self.db.execute("SELECT COUNT(*) FROM outbox WHERE sent=1").fetchone()[0] >= self.max_sent
def _send(self, mail_from, to, subject, body, in_reply_to):
m = EmailMessage()
m["From"], m["To"], m["Subject"] = mail_from, to, subject
@ -219,8 +229,10 @@ if __name__ == "__main__":
ap.add_argument("--db", default="tickets.sqlite3")
ap.add_argument("--listen", default="127.0.0.1:8025")
ap.add_argument("--smtp", metavar="HOST:PORT", help="send replies via this SMTP relay")
ap.add_argument("--max-sent", type=int, metavar="N",
help="stop sending once N replies have been sent in total (kept in the db)")
a = ap.parse_args()
smtp = (a.smtp.rsplit(":", 1)[0], int(a.smtp.rsplit(":", 1)[1])) if a.smtp else None
h, p = a.listen.rsplit(":", 1)
print(f"ticket service on http://{a.listen}/mas-events")
make_server(TicketService(a.db, smtp=smtp), h, int(p)).serve_forever()
make_server(TicketService(a.db, smtp=smtp, max_sent=a.max_sent), h, int(p)).serve_forever()