diff --git a/goodnews/api.py b/goodnews/api.py index 5e79ebb..72d9f21 100644 --- a/goodnews/api.py +++ b/goodnews/api.py @@ -845,6 +845,8 @@ def create_app() -> FastAPI: message = (body.message or "").strip()[:4000] if not message: raise HTTPException(status_code=422, detail="Reply message is required.") + # 1. Validate + gather, then release the DB connection — SMTP can take + # ~20s and shouldn't keep a connection open. with get_conn() as conn: admin = _require_admin(conn, request) fb = conn.execute("SELECT contact_email, message FROM feedback WHERE id = ?", (fid,)).fetchone() @@ -852,15 +854,18 @@ def create_app() -> FastAPI: raise HTTPException(status_code=404, detail="feedback not found") if not fb["contact_email"]: raise HTTPException(status_code=400, detail="No reply address for this feedback.") - # Send first — only record a reply that actually went out, so the UI - # can keep the draft on failure. - try: - email_send.send_feedback_reply(fb["contact_email"], message, fb["message"]) - except Exception as exc: # noqa: BLE001 — surface any SMTP failure to the admin - raise HTTPException(status_code=502, detail=f"Could not send the reply: {exc}") + admin_id, to, original = admin["id"], fb["contact_email"], fb["message"] + # 2. Send with no DB connection held; only record a reply that actually + # went out, so the UI can keep the draft on failure. + try: + email_send.send_feedback_reply(to, message, original) + except Exception as exc: # noqa: BLE001 — surface any SMTP failure to the admin + raise HTTPException(status_code=502, detail=f"Could not send the reply: {exc}") + # 3. Record the sent reply + mark the item read. + with get_conn() as conn: cur = conn.execute( "INSERT INTO feedback_replies (feedback_id, user_id, message, sent_to) VALUES (?, ?, ?, ?)", - (fid, admin["id"], message, fb["contact_email"]), + (fid, admin_id, message, to), ) conn.execute( "UPDATE feedback SET read_at = COALESCE(read_at, CURRENT_TIMESTAMP) WHERE id = ?", (fid,)