Cover letter creation
Builds cover letters from a job description and candidate profile, with a preview panel and AI edit pass.
Repository layout#
The system spans backend FastAPI routes and LangChain-based services, a job description fetcher agent, and a React-based frontend with dedicated components and hooks for form input, preview, and editing.
graph TB subgraph "Frontend" A["CoverLetterDetailsForm.tsx"] B["GeneratedLetterPanel.tsx"] C["page.tsx"] D["cover-letter.service.ts"] E["use-cover-letters.ts"] F["cover-letter.ts"] end subgraph "Backend" G["routes/cover_letter.py"] H["services/cover_letter.py"] I["models/cover_letter/schemas.py"] J["agents/web_content_agent.py"] K["services/ats.py"] L["data/prompt/ats_analysis.py"] end A --> C B --> C C --> D D --> G G --> H H --> J H --> I C --> E C --> F K --> L
Building blocks#
- Backend request/response models define the shape of inputs and outputs for cover letter generation and editing.
- The cover letter service composes prompts, resolves job descriptions from URLs or text, and invokes an LLM to produce plain-text cover letters.
- FastAPI routes accept multipart/form-data, assemble resume data, and delegate to the service layer.
- The frontend has a form for collecting inputs, a preview panel with copy/download/edit controls, and React Query hooks for optimistic UI and state management.
- A job description fetcher agent retrieves clean markdown from URLs via an external service.
- ATS-related services and prompts support keyword coverage, formatting, and compatibility analysis.
How it fits together#
The system follows a layered architecture:
- Frontend collects inputs and renders previews.
- API routes validate and forward requests to the service layer.
- Services resolve job descriptions, build prompts, and call the LLM.
- Responses are returned as plain text cover letters.
sequenceDiagram participant UI as "page.tsx" participant SVC as "cover-letter.service.ts" participant API as "routes/cover_letter.py" participant SRV as "services/cover_letter.py" participant AG as "agents/web_content_agent.py" UI->>SVC : "generateCoverLetter(formData)" SVC->>API : "POST /api/cover-letter" API->>SRV : "generate_cover_letter(resume_data, inputs)" SRV->>AG : "_resolve_job_description(jd_url)" AG-->>SRV : "markdown jd" SRV-->>API : "plain text cover letter" API-->>SVC : "CoverLetterResponse" SVC-->>UI : "render GeneratedLetterPanel"
Backend: cover letter generation and editing#
- Request/response models encapsulate inputs and outputs for generation and editing workflows.
- The generation service:
- Resolves job descriptions from a URL (via markdown fetcher) or uses provided text, merging both when available.
- Formats a strict prompt with constraints on length, paragraph count, tone, and content focus.
- Calls the LLM with a role instruction and returns plain text.
- Editing service:
- Accepts a previous cover letter and user edit instructions.
- Enforces strict adherence to instructions while preserving quality and constraints.
flowchart TD Start(["Start"]) --> ResolveJD["Resolve Job Description<br/>URL or Text"] ResolveJD --> BuildPrompt["Build Cover Letter Prompt<br/>with constraints"] BuildPrompt --> InvokeLLM["Invoke LLM with role"] InvokeLLM --> Output["Plain Text Cover Letter"] Output --> End(["End"])
Backend: job description resolution#
- When a URL is provided, the system fetches clean markdown content and prepends any additional context text.
- Ensures reliability by returning empty strings on failure and normalizing protocol prefixes.
flowchart TD
A["jd_url or job_description"] --> B{"jd_url provided?"}
B -- Yes --> C["Normalize URL"]
C --> D["Fetch markdown via agent"]
D --> E{"Fetched content?"}
E -- Yes --> F["Append manual context if present"]
E -- No --> G["Use manual context only"]
B -- No --> H["Use manual context only"]
F --> I["Resolved JD"]
G --> I
H --> IBackend: routes and data flow#
- Routes accept form-encoded inputs, construct a minimal resume data object, and call the service layer.
- Both generation and editing endpoints return a unified response model.
sequenceDiagram participant Client as "Frontend" participant Route as "routes/cover_letter.py" participant Service as "services/cover_letter.py" Client->>Route : "POST /cover-letter/generator/" Route->>Service : "generate_cover_letter(...)" Service-->>Route : "body" Route-->>Client : "CoverLetterResponse" Client->>Route : "POST /cover-letter/edit/" Route->>Service : "edit_cover_letter(...)" Service-->>Route : "body" Route-->>Client : "CoverLetterResponse"
Frontend: inputs and preview#
- CoverLetterDetailsForm collects:
- Personal details (your name, desired role/goal).
- Job description via URL or text, with a toggle to switch modes.
- Key points to highlight and additional context.
- Optional recipient and company details.
- GeneratedLetterPanel displays the cover letter, supports copy/download, and toggles an edit mode with instructions.
- The page orchestrates state, validation, and submission via React Query mutations.
classDiagram
class CoverLetterDetailsForm {
+formData
+handleInputChange(field,value)
+switchMode(mode)
}
class GeneratedLetterPanel {
+generatedLetter
+editMode
+setEditMode(mode)
+editInstructions
+setEditInstructions(text)
+isEditing
+editCoverLetter()
+copyToClipboard(text)
+downloadAsText()
}
class CoverLetterPage {
+formData
+generatedLetter
+generateCoverLetter()
+editCoverLetter()
+copyToClipboard()
+downloadAsText()
}
CoverLetterPage --> CoverLetterDetailsForm : "renders"
CoverLetterPage --> GeneratedLetterPanel : "renders"Frontend: service and hooks#
- cover-letter.service.ts defines typed endpoints for generation and editing.
- use-cover-letters.ts exposes React Query hooks for fetching sessions, deleting entries, and mutating generation/editing requests.
- Types cover-letter.ts define session and entry shapes for persistence and display.
graph LR A["use-cover-letters.ts"] --> B["cover-letter.service.ts"] B --> C["page.tsx"] C --> D["GeneratedLetterPanel.tsx"] C --> E["CoverLetterDetailsForm.tsx"]
Prompt engineering strategies#
- Constraints keep output concise, readable, and ATS-friendly output:
- Word and paragraph limits.
- Specific opening referencing a concrete JD element.
- Middle section aligns 1-2 resume qualifications to stated requirements.
- Closing remains modest and concrete.
- Tone is confident yet not overly eager.
- Plain text output avoids markdown or JSON.
- Editing prompt enforces strict adherence to user instructions while preserving quality and constraints.
flowchart TD P["Prompt Template"] --> C1["Word limit"] P --> C2["Paragraph count"] P --> C3["Opening directive"] P --> C4["Middle alignment"] P --> C5["Closing tone"] P --> C6["Plain text output"] P --> C7["Do not invent info"]
Integration with job description analysis and candidate matching#
- ATS analysis service evaluates keyword coverage, formatting, and compatibility, producing structured metrics and recommendations.
- While cover letter generation focuses on narrative, ATS analysis can guide resume refinement and JD alignment, complementing cover letter personalization.
graph TB A["ATS Analysis Prompt"] --> B["LLM Chain"] B --> C["Structured Metrics"] C --> D["Recommendations"] D --> E["Integration Point"]
Customization options#
- Tailor to roles and companies via:
- Recipient and company names.
- Job description URL or text.
- Additional context and key points to highlight.
- Language selection for output.
- Editing allows iterative refinement with explicit instructions.
Content structure#
- Opening: Reference one specific JD aspect (product, tech, or problem).
- Middle: Align 1-2 relevant resume qualifications to stated requirements.
- Closing: Brief availability to discuss without desperation.
- Tone: Confident peer; avoid placeholders and invented facts.
Editing and revision workflows#
- Toggle edit mode, provide instructions, and apply changes to refine content.
- Validation requires a cover letter exists and instructions are provided before editing.
sequenceDiagram participant UI as "GeneratedLetterPanel.tsx" participant Hook as "use-cover-letters.ts" participant API as "routes/cover_letter.py" participant Service as "services/cover_letter.py" UI->>Hook : "useEditCoverLetter()" Hook->>API : "POST /cover-letter/edit/" API->>Service : "edit_cover_letter(previous, instructions)" Service-->>API : "updated body" API-->>Hook : "CoverLetterResponse" Hook-->>UI : "new body"
ATS optimization and formatting considerations#
- The cover letter output is plain text, avoiding markdown or JSON, which improves ATS compatibility.
- Constraints prevent problematic characters and enforce concise formatting.
- ATS analysis provides complementary insights for keyword coverage and formatting improvements.
Frontend composition and preview functionality#
- Resume selection supports choosing an existing resume or uploading a file.
- Analysis data can preload the form.
- Copy-to-clipboard and download-as-text enable easy reuse.
- Loading overlays and toasts provide feedback during generation and editing.
Dependencies#
- Frontend depends on typed services and hooks for network operations.
- Backend routes depend on the cover letter service and the job description agent.
- ATS analysis is decoupled and can be used independently for resume optimization.
graph LR FE["page.tsx"] --> SVC["cover-letter.service.ts"] SVC --> ROUTE["routes/cover_letter.py"] ROUTE --> SRV["services/cover_letter.py"] SRV --> AG["agents/web_content_agent.py"] SRV --> SCHEMA["models/cover_letter/schemas.py"] BEATSA["services/ats.py"] --> BEATSP["data/prompt/ats_analysis.py"]
Performance#
- Minimizing prompt size and enforcing strict constraints reduces token usage and latency.
- Fetching job descriptions from URLs introduces network latency; caching or pre-fetching can help.
- Plain text output avoids extra parsing overhead for downstream consumers.
Troubleshooting#
- Job description URL errors: Ensure the URL is reachable and returns content; the agent returns empty content on failure.
- Missing inputs: The page validates presence of required fields before submitting.
- Editing failures: Confirm a cover letter exists and edit instructions are provided.
- ATS mismatch: Use ATS analysis to identify missing keywords and formatting gaps; iterate on resume and cover letter alignment.
Appendix#
- Example prompt constraints and requirements are embedded in the service layer and enforced during generation and editing.
- ATS analysis provides concrete feedback for keyword coverage and formatting improvements.