How the Cover Letter Is Generated in MadsLorentzen/ai-job-search: Template & Workflow Explained
This repository generates cover letters using a custom LaTeX document class (cover.cls) with AI-populated placeholders, compiling to PDF via XeLaTeX.
The MadsLorentzen/ai-job-search project automates job applications by combining LLM-generated content with a structured LaTeX workflow. Understanding how the cover letter is generated—and which template powers it—reveals a clean separation between content creation and professional typesetting.
The Core Template: cover.cls Custom LaTeX Class
The foundation of every generated cover letter is cover.cls, a custom LaTeX document class located at cover_letters/cover.cls. This class defines the visual structure through specialized macros:
\namesection{}— Formats candidate contact information\lettercontent{}— Wraps body paragraphs with proper spacing\closing{}— Handles sign-off placement- Custom font integration — Loads Lato and Raleway typefaces via XeLaTeX
The class abstracts LaTeX complexity so the AI only populates content, not formatting.
Template Structure and Placeholder System
The definitive guide resides at .claude/skills/job-application-assistant/06-cover-letter-templates.md (lines 9-44). This documentation specifies:
Required placeholders:
[YOUR_NAME][YOUR_EMAIL][COMPANY][ROLE]
Mandatory sections:
- Title/date block
- Personalized greeting
- Opening paragraph (hook)
- Body paragraphs (experience alignment)
- Bullet list (key qualifications)
- Closing paragraph
- Professional signature
Critical LaTeX constraint: The itemize environment cannot nest inside \lettercontent{}—bullets require separate handling (lines 31-44).
File Naming and Output Convention
Generated .tex files follow a strict pattern (line 11 of the guide):
cover_letters/cover_<company>_<role>.tex
Example: cover_acme-corp_data-engineer.tex
This convention enables automated file management and links generated content to specific job applications.
The Generation Workflow
The cover letter generation proceeds through three stages:
- LLM content generation — The AI produces tailored letter text using the template guidelines as system context
- Template rendering — Placeholders are replaced with candidate data and LLM output
- PDF compilation — XeLaTeX renders the final document
from pathlib import Path
import subprocess
def generate_cover_letter(company: str, role: str, candidate_data: dict) -> Path:
"""
Generate a cover letter using the cover.cls LaTeX template.
"""
# Stage 1: Generate content via LLM with template constraints
letter_body = llm.generate(
system_prompt=load_template_guide(), # 06-cover-letter-templates.md
user_prompt=f"Write cover letter for {role} at {company}"
)
# Stage 2: Render LaTeX with populated placeholders
tex_filename = f"cover_{company.lower().replace(' ', '-')}_{role.lower().replace(' ', '-')}.tex"
tex_path = Path("cover_letters") / tex_filename
latex_content = render_latex_template(
template_path=Path("cover_letters/cover_example.tex"),
placeholders={
"[YOUR_NAME]": candidate_data["name"],
"[YOUR_EMAIL]": candidate_data["email"],
"[COMPANY]": company,
"[ROLE]": role,
"[BODY]": letter_body
}
)
tex_path.write_text(latex_content)
# Stage 3: Compile to PDF
subprocess.run([
"xelatex",
"-interaction=nonstopmode",
tex_path.name
], cwd=tex_path.parent, check=True)
return tex_path.with_suffix(".pdf")
The render_latex_template() function performs simple string substitution—no complex templating engine required.
Compilation Command
Per lines 17-19 of the template guide, the exact compilation command is:
xelatex -interaction=nonstopmode cover_<company>_<role>.tex
The -interaction=nonstopmode flag ensures batch processing continues despite minor warnings—essential for automation pipelines.
Key Files in the Cover Letter System
| File | Purpose |
|---|---|
cover_letters/cover.cls |
Custom LaTeX class defining document structure and styling |
cover_letters/cover_example.tex |
Reference implementation showing fully-rendered output |
.claude/skills/job-application-assistant/06-cover-letter-templates.md |
Authoritative template documentation and constraints |
tools/security_guards.py (lines 64-66) |
Security policy inclusion for cover letter file handling |
Summary
- Template engine: Custom
cover.clsLaTeX class with Lato/Raleway typography - Content source: LLM generation guided by
06-cover-letter-templates.mdconstraints - Placeholder system: Bracketed tokens (
[COMPANY],[ROLE], etc.) replaced at render time - Output format: XeLaTeX-compiled PDF with standardized filename convention
- Critical constraint: Bullet lists cannot reside inside
\lettercontent{}macros
Frequently Asked Questions
What LaTeX engine is required for cover letter compilation?
XeLaTeX is required. The cover.cls class depends on XeLaTeX's font handling capabilities to load Lato and Raleway typefaces (lines 9-13 of the template guide). Standard pdfLaTeX will fail due to font specification incompatibilities.
Where is the actual cover letter content generated?
Content generation is orchestrated through Claude commands in the .claude/commands/ directory, with the specific constraints and section requirements defined in .claude/skills/job-application-assistant/06-cover-letter-templates.md. The LLM receives these guidelines as system context before producing letter text.
Can I modify the visual styling of generated cover letters?
Yes—edit cover_letters/cover.cls directly. This centralizes all formatting decisions: margins, colors, font weights, and section spacing. Changes propagate to all future generated letters without template modifications, since content remains decoupled from presentation.
Why does the template prohibit itemize inside \lettercontent{}?
The \lettercontent{} macro applies specific vertical spacing and alignment rules that conflict with LaTeX's list environment internals. The guide documents this limitation (lines 31-44) to prevent compilation errors—bullet points must use alternative formatting or separate macro calls.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →