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:

  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
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.cls LaTeX class with Lato/Raleway typography
  • Content source: LLM generation guided by 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. 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →