# How the Cover Letter Is Generated in MadsLorentzen/ai-job-search: Template & Workflow Explained

> Learn how MadsLorentzen/ai-job-search generates cover letters using a custom LaTeX template and AI placeholders, compiling to PDF with XeLaTeX. Explore the workflow.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: how-to-guide
- Published: 2026-08-30

---

**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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.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:**
1. Title/date block
2. Personalized greeting
3. Opening paragraph (hook)
4. Body paragraphs (experience alignment)
5. Bullet list (key qualifications)
6. Closing paragraph
7. 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:

1. **LLM content generation** — The AI produces tailored letter text using the template guidelines as system context
2. **Template rendering** — Placeholders are replaced with candidate data and LLM output
3. **PDF compilation** — XeLaTeX renders the final document

```python
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:

```bash
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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-application-assistant/06-cover-letter-templates.md) | Authoritative template documentation and constraints |
| [`tools/security_guards.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/tools/security_guards.py) (lines 64-66) | Security policy inclusion for cover letter file handling |

## Summary

- **Template engine:** Custom `cover.cls` LaTeX class with Lato/Raleway typography
- **Content source:** LLM generation guided by [`06-cover-letter-templates.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/06-cover-letter-templates.md) constraints
- **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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.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.