Health insurance claim denials are common in the US, and the fix — a formal written appeal — is something most people never write because they don't know the format. AppealFlow is a free tool that turns a denial letter or Explanation of Benefits (EOB) into an editable appeal letter you can download as PDF or Word, with no account.
From a developer's point of view it's a classic document pipeline: messy input → structured fields → templated letter → export. Here's how to think about each stage.
Stage 1: Two ways in — upload or type
Users arrive with very different inputs: a photo of a letter, a PDF EOB, or just a claim number scribbled on a note. So there are two entry paths that converge on the same data shape:
- Upload a PDF, PNG or JPG and extract text with OCR.
- Manual entry for carrier, claim number and denial reason.
interface DenialDetails {
patientName: string;
carrier: string;
claimNumber: string;
denialDate?: string; // ISO date
denialReason: string; // "not medically necessary", "no prior auth", ...
reasonCode?: string; // e.g. CO-50
serviceDescription: string;
}
The key design decision: OCR output is never trusted blindly. It pre-fills a form, and the user confirms every field before a letter is generated. OCR on phone photos of letters is good, but not good enough to send to an insurer unreviewed.
Stage 2: Denial type drives the template
A "not medically necessary" denial needs a different argument than a missing prior authorization or a surprise out-of-network ER bill. Rather than one mega-template, each denial type gets its own generator page and argument structure — medical necessity, prior auth, step therapy exceptions, pharmacy/PBM formulary denials, imaging (MRI/CT/PET), dental, GLP-1 prior auth, No Surprises Act bills, and even financial-aid appeals.
type DenialType =
| "medical_necessity" | "prior_auth" | "step_therapy"
| "pharmacy" | "imaging" | "dental" | "surprise_bill";
const templates: Record<DenialType, (d: DenialDetails) => Section[]> = {
medical_necessity: (d) => [intro(d), clinicalJustification(d), policyRebuttal(d), requestReview(d)],
prior_auth: (d) => [intro(d), authTimeline(d), urgency(d), requestReview(d)],
// ...
};
Keeping templates as arrays of small section builders makes them easy to test and reuse.
Stage 3: Optional legal citations as toggles
Appeals are stronger when they reference the rules the insurer must follow — for example ERISA §503 (full and fair review for employer plans), ACA §2719 (internal appeals and external review) and the No Surprises Act. These are modeled as toggleable blocks rather than hardcoded text, so users can include only what applies to their plan.
const citations = {
erisa503: "Under ERISA §503, I am entitled to a full and fair review of this claim.",
aca2719: "Under ACA §2719, I request an internal appeal and, if needed, external review.",
nsa: "This bill appears to be subject to the No Surprises Act protections.",
};
Stage 4: Live editing, then export
The generated draft is just a starting point, so it opens in an editor where every line can be changed. Export produces PDF or Word, and nothing requires a login. Being able to edit before export matters more than the quality of the first draft — people know details about their own case that no template can.
Things I'd stress for anyone building in this space
- Don't promise outcomes. A letter improves the odds of a fair review; it doesn't guarantee approval. Say so on every page.
- Deadlines are a feature. Most plans give 60–180 days to appeal — surface that prominently.
- Treat uploads as sensitive. Denial letters contain health information; collect the minimum and don't require accounts.
- Human review beats clever automation for anything that gets mailed to an insurer.
If you (or someone you know) is dealing with a denial, the generator is free at appealflow.net. I'd love to hear from others building document-generation tools — especially how you handle OCR confidence in the UI.
Top comments (0)