- analysis.py: deterministic claim scorer + /analyze /approve /letter /advance-tier endpoints (auto-runs on intake) - packet.py + packet_fields.json: welcome-packet templating engine (6 onboarding docs, field catalog) - letterstream.py + letters.py: certified-mail send pipeline + letter lifecycle (webhook verified) - docuseal.py: DocuSeal signing integration - staff.py/models.py/schema.sql/auth.py: approval actor from staff key, tier gate (APPROVED+ACTIVE+onboarding docs), onboarding_docs table - frontend/: dependency-free static portal (intake, magic-link login/verify, dashboard) - landing-mockups/: 4 design-stance mockups + favicons - legal/: aup/privacy/sms-terms/terms HTML - docs/: letter-queue scope, letterstream API contract, 6 welcome-packet templates - review-dre-landing-2026-08-21.md: 3-variant landing feedback sprint - compliance/DRE_Compliance_Manual.md: updated Source synced from deployed /opt/dre-portal/app/ (was 4 days ahead of git).
6.9 KiB
Letter Queue - Backend Scope (draft)
Date: 2026-08-24 Author: Sho'Nuff (draft for Germaine review)
Current state (ground truth, verified 2026-08-24)
- Send pipeline is BUILT and verified end-to-end. Two new modules:
/opt/dre-portal/app/letterstream.py— LetterStream integration (auth, account status, send single/batch, preauth/doauth, tracking, signature, proof)./opt/dre-portal/app/letters.py— router: letters queue lifecycle, PDF render (fpdf2 2.8.8), LetterStream send, and the callback receiver.
- Live DB tables exist:
lettersandletter_events(added toschema.sql, applied on restart). - LetterStream API contract fully recovered from the user-supplied
api_fulfillment.pdf(21 pages):POST https://www.letterstream.com/apis/witha=api_id,h=hash,t=unique_id. Hash =md5(base64_encode(last6(t) . api_key . first6(t))). Verified three ways: PDF formula + liveAUTHOK+ user screenshot sample vector. - Account live-verified: balance
$100.00,testmode=disabled. No documents submitted, no charges incurred. - Letter content still lives on
claims(subject/body/tier) and is generated byanalysis.recommend_letter()(deterministic, no LLM) — thelettersqueue is the physical-send layer on top. - Queue UI is wired and smoke-tested (2026-08-25, task ls7).
letter-queue.htmlis a functional queue: filterable list, status badges, Approve / Price & Queue / Confirm & Mail / Track actions, and a New Letter modal (claim picker + structured recipient + mail class).GET /api/staff/lettersnow joinsclaimsto exposeclaim_number. Verified via live API lifecycle + Node render harness + 9-page HTTP smoke (all 200). - LetterStream callback is NOT enabled yet — the receiver is live and tested, but the callback toggle in the LetterStream dashboard stays off until after the first real send is confirmed.
Target state
A durable letter queue: many letters per claim, each with a lifecycle (draft -> approved -> queued -> sent -> delivered/failed), sendable via LetterStream certified mail, with FDCPA-compliant content and a full audit trail.
1. Data model - new letters table
| Column | Type | Purpose |
|---|---|---|
| id | TEXT PK | uuid4 |
| claim_id | TEXT FK -> claims.id | owning claim |
| letter_type | TEXT | demand / escalation / validation / custom |
| tier | TEXT | tier the letter was generated for |
| subject | TEXT | letter subject |
| body | TEXT | letter body (markdown or plain) |
| status | TEXT | draft / approved / queued / sending / sent / delivered / failed |
| recipient_name | TEXT | debtor or registered-agent name |
| addr1, addr2, city, state, zip | TEXT | mailing address |
| letterstream_job_id | TEXT | LetterStream job reference (nullable) |
| tracking_number | TEXT | USPS tracking (nullable) |
| sent_at | TEXT | ISO timestamp (nullable) |
| delivered_at | TEXT | ISO timestamp (nullable) |
| error | TEXT | last send error (nullable) |
| created_by | TEXT | RBAC actor name |
| created_at / updated_at | TEXT | ISO timestamps |
Migration: keep the four claim columns as the "current draft" during transition, then deprecate them once the queue is live. No destructive drop until the queue is proven in production.
2. API
GET /api/staff/claims/{n}/letters- list letters for a claimPOST /api/staff/claims/{n}/letters/generate- generate a draft row viarecommend_letter()(inserts, does not overwrite the claim columns)PUT /api/staff/letters/{id}- edit a draftPOST /api/staff/letters/{id}/queue- mark queued (requires full mailing address)POST /api/staff/letters/{id}/send- call LetterStream, store job_id + trackingPOST /api/letters/webhook- LetterStream status callback (delivered / failed)GET /api/staff/letters- global queue across claims (feeds letter-queue.html)
Idempotency: send is guarded by status (only queued -> sending), so a
double-click cannot mail a letter twice. Store letterstream_job_id before
marking sent.
3. Send pipeline (LetterStream)
Order of work:
- Verify the existing
LETTERSTREAM_API_KEYagainst their API (is it valid, what account, what products are enabled). - Map their REST surface: auth method, endpoint shape, certified vs first-class, PDF upload vs HTML/plain rendering, return address handling, tracking + status webhook. Do not assume - confirm from their docs or a test call.
- PDF generation: render the letter body (reportlab or weasyprint) with DRE letterhead, or pass content to LetterStream to render.
- Address handling: return address (DRE office / PO box) and debtor mailing address must both be resolved before send.
- Status sync: webhook or poll updates
status,tracking_number,delivered_at.
4. Compliance (FDCPA)
- Every first-contact letter MUST carry the 1692g validation notice: amount of debt, creditor name, 30-day dispute right, right to request verification.
- No false, deceptive, or misleading language (1692e); no threats of action DRE does not intend to take.
- Human sign-off gate: a letter cannot move to
queueduntil status isapproved(actor recorded). - Full immutable audit log (actor + timestamp + old/new) - the
audit_logpattern already exists and extends here.
5. UI (letter-queue.html) — DONE 2026-08-25 (ls7)
/var/www/internal/letter-queue.html is live:
- filterable list (status) with per-status summary chips
- Approve (DRAFT) / Price & Queue (APPROVED/PREAUTH/ERROR) / Confirm & Mail (PREAUTH) / Reject / Cancel / Track actions
- status badges (DRAFT/APPROVED/PREAUTH/SENT/REJECTED/CANCELLED/ERROR)
- tracking timeline (USPS scan events) in the detail panel
- New Letter modal: claim picker (prefills debtor name), structured recipient, mail class
Reject/cancel implemented 2026-08-25: POST /api/staff/letters/{id}/reject (requires reason) and
POST /api/staff/letters/{id}/cancel (optional reason) move DRAFT/APPROVED/PREAUTH/ERROR letters to
REJECTED/CANCELLED, persist the reason in letters.note, and write an audit_log row. Guards return 409
for SENT/REJECTED/CANCELLED and 422 for a missing reject reason. Queue UI has Reject (red) / Cancel buttons
for all non-terminal states.
6. Decisions locked (2026-08-24)
- LetterStream key — valid and live;
$100.00balance,testmode=disabled. - Return address — Germaine provides it 2026-08-25. Set as
LETTERSTREAM_RETURN_ADDRESSin.env; drafting does NOT block on it, onlysenddoes (clean 409 until configured). - Signatory —
Debt Recovery Experts LLC(no named individual). Default in code; overridable viaLETTERSTREAM_SIGNATORYin.env. - Address verification / NCOA — none. Pull debtor/return-address data from the client's claim info + Super Search.
- FDCPA 1692g notice — mandatory on first contact (DRE is a third-party debt
collector). Content is generated by
analysis.recommend_letter(); final validation- notice wording vs welcome-packet copy still to be finalized (task ls8).