{"record":{"id":"3b28fe99614c8260","repo":"odysseus-dev/odysseus","slug":"document-has-no-source-email-cannot-reply","errorCode":null,"errorMessage":"Document has no source email — cannot reply","messagePattern":"Document has no source email — cannot reply","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"routes/document/document_routes.py","lineNumber":1651,"sourceCode":"        from src.pdf_forms import fill_fields, stamp_signatures, stamp_annotations\n        from core.database import Signature\n        # COMPOSE_UPLOADS_DIR lives in email_routes — re-derive here so we\n        # don't import from a routes file (cycle-prone). Same env override\n        # as email_routes (ODYSSEUS_MAIL_ATTACHMENTS_DIR).\n        from pathlib import Path as _Path\n        _COMPOSE_DIR = _Path(MAIL_ATTACHMENTS_DIR) / \"_compose\"\n        _COMPOSE_DIR.mkdir(parents=True, exist_ok=True)\n\n        user = get_current_user(request)\n        db = SessionLocal()\n        try:\n            doc = db.query(Document).filter(Document.id == doc_id).first()\n            if not doc:\n                raise HTTPException(404, \"Document not found\")\n            _verify_doc_owner(db, doc, user)\n\n            if not (doc.source_email_uid and doc.source_email_folder):\n                raise HTTPException(400, \"Document has no source email — cannot reply\")\n\n            # 1) Build the flattened PDF (same pipeline as export_pdf)\n            upload_id = find_source_upload_id(doc.current_content or \"\")\n            if not upload_id:\n                raise HTTPException(400, \"Document is not linked to a source PDF\")\n            pdf_path = _locate_current_user_upload(request, upload_id, user)\n            if not pdf_path:\n                raise HTTPException(404, f\"Source PDF {upload_id} not found\")\n\n            schema = load_field_sidecar(pdf_path) or []\n            sig_field_names = {f[\"name\"] for f in schema if f.get(\"type\") == \"signature\"}\n            all_values = parse_markdown_to_values(doc.current_content or \"\")\n            text_values: dict = {}\n            sig_ids: dict[str, str] = {}\n            for name, raw in all_values.items():\n                if name in sig_field_names and isinstance(raw, str) and raw.startswith(\"signature:\"):\n                    sig_ids[name] = raw[len(\"signature:\"):].strip()\n                elif name not in sig_field_names:","sourceCodeStart":1633,"sourceCodeEnd":1669,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/document/document_routes.py#L1633-L1669","documentation":"Error \"Document has no source email — cannot reply\" thrown in odysseus-dev/odysseus.","triggerScenarios":"Triggered when the corresponding server-side validation or runtime check at the recorded location rejects the request or operation and returns this error message to the caller.","commonSituations":"See trigger scenarios.","solutions":["Only use reply on documents created from an email source.","Open the original email and reply from there instead."],"exampleFix":null,"handlingStrategy":null,"validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}