- 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).
137 lines
6.9 KiB
Markdown
137 lines
6.9 KiB
Markdown
# 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:** `letters` and `letter_events` (added to `schema.sql`,
|
|
applied on restart).
|
|
- **LetterStream API contract fully recovered** from the user-supplied
|
|
`api_fulfillment.pdf` (21 pages): `POST https://www.letterstream.com/apis/` with
|
|
`a`=api_id, `h`=hash, `t`=unique_id. Hash = `md5(base64_encode(last6(t) . api_key
|
|
. first6(t)))`. Verified three ways: PDF formula + live `AUTHOK` + 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 by
|
|
`analysis.recommend_letter()` (deterministic, no LLM) — the `letters` queue is the
|
|
physical-send layer on top.
|
|
- **Queue UI is wired and smoke-tested (2026-08-25, task ls7).** `letter-queue.html` is
|
|
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/letters` now joins `claims` to expose
|
|
`claim_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 claim
|
|
- `POST /api/staff/claims/{n}/letters/generate` - generate a draft row via
|
|
`recommend_letter()` (inserts, does not overwrite the claim columns)
|
|
- `PUT /api/staff/letters/{id}` - edit a draft
|
|
- `POST /api/staff/letters/{id}/queue` - mark queued (requires full mailing address)
|
|
- `POST /api/staff/letters/{id}/send` - call LetterStream, store job_id + tracking
|
|
- `POST /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:
|
|
|
|
1. Verify the existing `LETTERSTREAM_API_KEY` against their API (is it valid,
|
|
what account, what products are enabled).
|
|
2. 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.
|
|
3. PDF generation: render the letter body (reportlab or weasyprint) with DRE
|
|
letterhead, or pass content to LetterStream to render.
|
|
4. Address handling: return address (DRE office / PO box) and debtor mailing
|
|
address must both be resolved before send.
|
|
5. 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 `queued` until status is
|
|
`approved` (actor recorded).
|
|
- Full immutable audit log (actor + timestamp + old/new) - the `audit_log`
|
|
pattern 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)
|
|
|
|
1. **LetterStream key** — valid and live; `$100.00` balance, `testmode=disabled`.
|
|
2. **Return address** — Germaine provides it 2026-08-25. Set as
|
|
`LETTERSTREAM_RETURN_ADDRESS` in `.env`; drafting does NOT block on it, only
|
|
`send` does (clean 409 until configured).
|
|
3. **Signatory** — `Debt Recovery Experts LLC` (no named individual). Default in code;
|
|
overridable via `LETTERSTREAM_SIGNATORY` in `.env`.
|
|
4. **Address verification / NCOA** — none. Pull debtor/return-address data from the
|
|
client's claim info + Super Search.
|
|
5. **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).
|