Real IMAP connection via imapflow library: - Connects to IMAP server (IMAPFlow, TLS on port 993) - Fetches recent messages (configurable limit, default 20) - Extracts: from, fromEmail, subject, date, snippet (200 chars) - Thread-to-contact matching (PRD §6.1): 1. Match sender email against persons table 2. If no person match, extract domain and match against company.website 3. Returns matchedCompany + matchedPerson for each email - Audit-logged (action: crm.mailsync:imap) - Zod validated (host, port, user, password, folder, limit) - 502 on IMAP connection failure with detail - 400 on validation failure Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
10 KiB
Executable File
10 KiB
Executable File
Resume / CV Template (ATS-Friendly)
Single-column, clean layout optimised for Applicant Tracking Systems (ATS). No graphics, no sidebars, no colour blocks - just well-structured text that parses perfectly by HR software.
When to use this template:
- Applying to corporate jobs through online portals
- Any scenario where the PDF will be machine-parsed before a human reads it
- When the recruiter explicitly asks for "a standard resume"
When NOT to use (use Academic brief instead):
- Creative/design industry positions → Academic brief (AltaCV-style)
- Academic CV with publications → Academic brief (Academic CV)
Resume Design Rules
- Target 1 page unless user specifies otherwise
- Margins:
left=1.5cm, right=1.5cm, top=1.5cm, bottom=1.5cm - No cover page - content starts immediately
- No TOC - too short for table of contents
- Font: FreeSerif (English) or Noto Sans SC (Chinese)
- Body font size: 10-10.5pt; Name: 22-26pt; Section titles: 13-14pt
- ⚠️ Minimum font size: 12px (9pt) - HARD FLOOR. No text in the entire resume may render smaller than 12px. This includes contact info, meta text, footnotes, and captions. Anything below 12px is unreadable in print and fails accessibility checks.
- Section separator: thin horizontal rule (
HRFlowable) - Bullet style:
•with tight spacing
Resume Line-Break Rules (Language-Aware)
- English: Prefer breaking at word boundaries (spaces, hyphens). If a long word must be split to avoid excessive whitespace, break at a valid syllable boundary and insert a hyphen (
-) - this is standard typographic practice (e.g.,experi-\nence,develop-\nment). ReportLab supportswordWrap='CJK'only for CJK content; for English use default paragraph wrapping withallowWidows=0, allowOrphans=0. - Chinese/CJK: Break allowed between any two CJK characters. Never break between a CJK character and its adjacent punctuation (、。,)》 etc. must stay with the preceding character.
- Mixed content (e.g., "Python 开发工程师"): Break at CJK boundaries or English word boundaries. Never split an English word in a CJK paragraph unless hyphenated.
- Contact line: Email, phone, location separated by
|or·. Each segment must stay on one line - if too long, move to next line at the separator, not mid-segment. - Dates and ranges: "Jan 2022 - Present" must stay as one unit. Never break a date range across lines.
Resume Page-Fill Rules (Anti-Blank-Space)
- Goal: Fill ≥85% of the page height. Content should reach at least the bottom quarter of the page. A resume that stops at 60% height with blank space below = FAIL.
- Adaptive spacing strategy (apply in order until page is ≥85% filled):
- Increase
spaceBefore/spaceAfteron section headers (from 10pt up to 18pt) - Increase
leading(line height) on body text (from 14pt up to 18pt) - Increase body
fontSizeby 0.5-1pt (from 10pt up to 11.5pt max) - Add a "Professional Summary" or "Key Achievements" section if none exists
- Increase margins slightly (from 1.5cm up to 2cm) to reduce line width and push content downward
- Increase
- Never leave a visible blank area > 3cm at the bottom of the page.
- If content overflows to page 2: do the reverse - reduce spacing, tighten leading (min 12pt), reduce fontSize (min 9pt / 12px), before removing content.
Complete Resume Template
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, HRFlowable, Table, TableStyle
)
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase.pdfmetrics import registerFontFamily
# ── Font Registration ──
pdfmetrics.registerFont(TTFont('FreeSerif', '/usr/share/fonts/truetype/freefont/FreeSerif.ttf'))
pdfmetrics.registerFont(TTFont('FreeSerif-Bold', '/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf'))
pdfmetrics.registerFont(TTFont('FreeSerif-Italic', '/usr/share/fonts/truetype/freefont/FreeSerifItalic.ttf'))
pdfmetrics.registerFont(TTFont('FreeSerif-BoldItalic', '/usr/share/fonts/truetype/freefont/FreeSerifBoldItalic.ttf'))
registerFontFamily('FreeSerif', normal='FreeSerif', bold='FreeSerif-Bold', italic='FreeSerif-Italic', boldItalic='FreeSerif-BoldItalic')
# For Chinese resumes, also register Noto Sans SC:
# pdfmetrics.registerFont(TTFont('Noto Sans SC', '/usr/share/fonts/truetype/chinese/NotoSansSC-Regular.ttf'))
# pdfmetrics.registerFont(TTFont('Noto Sans SC Bold', '/usr/share/fonts/truetype/chinese/NotoSansSC-Bold.ttf'))
# registerFontFamily('Noto Sans SC', normal='Noto Sans SC', bold='Noto Sans SC Bold')
# ── Styles ──
# ACCENT must come from palette.generate (Step 2)
# Run: python3 "$PDF_SKILL_DIR/scripts/pdf.py" palette.generate --title "Resume" --mode minimal
ACCENT = colors.HexColor('<accent from palette>') # Replace with palette output
name_style = ParagraphStyle(
'ResumeName', fontName='FreeSerif', fontSize=24,
leading=28, alignment=TA_CENTER, spaceAfter=2
)
contact_style = ParagraphStyle(
'ResumeContact', fontName='FreeSerif', fontSize=10,
leading=14, alignment=TA_CENTER, textColor=TEXT_MUTED, # From palette --c-muted
spaceAfter=8
)
section_title_style = ParagraphStyle(
'ResumeSectionTitle', fontName='FreeSerif', fontSize=13,
leading=16, spaceBefore=10, spaceAfter=4,
textColor=ACCENT
)
job_title_style = ParagraphStyle(
'ResumeJobTitle', fontName='FreeSerif', fontSize=11,
leading=14, spaceAfter=1
)
job_meta_style = ParagraphStyle(
'ResumeJobMeta', fontName='FreeSerif', fontSize=10,
leading=13, textColor=TEXT_MUTED, spaceAfter=4 # From palette --c-muted
)
bullet_style = ParagraphStyle(
'ResumeBullet', fontName='FreeSerif', fontSize=10,
leading=14, leftIndent=14, bulletIndent=0,
spaceBefore=1, spaceAfter=1
)
body_style = ParagraphStyle(
'ResumeBody', fontName='FreeSerif', fontSize=10,
leading=14, spaceAfter=2
)
# ── Helpers ──
def section_header(title):
"""Section title + thin rule separator."""
return [
Paragraph(f'<b>{title}</b>', section_title_style),
HRFlowable(width='100%', thickness=0.8, color=ACCENT,
spaceBefore=0, spaceAfter=6),
]
def experience_entry(title, company, dates, location, bullets):
"""One work experience block."""
elements = [
Paragraph(f'<b>{title}</b>', job_title_style),
Paragraph(f'{company} | {dates} | {location}', job_meta_style),
]
for b in bullets:
elements.append(Paragraph(f'• {b}', bullet_style))
elements.append(Spacer(1, 4))
return elements
def education_entry(degree, school, dates, details=None):
"""One education block."""
elements = [
Paragraph(f'<b>{degree}</b>', job_title_style),
Paragraph(f'{school} | {dates}', job_meta_style),
]
if details:
elements.append(Paragraph(details, body_style))
elements.append(Spacer(1, 4))
return elements
def skills_row(categories):
"""
Skills as compact label: value pairs.
categories = [('Programming', 'Python, Java, C++'), ('Tools', 'Git, Docker')]
"""
elements = []
for cat, vals in categories:
elements.append(Paragraph(f'<b>{cat}:</b> {vals}', body_style))
return elements
# ── Build Document ──
doc = SimpleDocTemplate(
'resume.pdf', pagesize=A4,
leftMargin=1.5*cm, rightMargin=1.5*cm,
topMargin=1.5*cm, bottomMargin=1.5*cm,
title='Resume - Your Name',
author='Z.ai', creator='Z.ai'
)
story = []
# Header
story.append(Paragraph('<b>YOUR NAME</b>', name_style))
story.append(Paragraph(
'email@example.com | +86 138-0000-0000 | Shanghai, China | github.com/yourname',
contact_style
))
# Summary
story.extend(section_header('PROFESSIONAL SUMMARY'))
story.append(Paragraph(
'Results-driven software engineer with 5+ years of experience in backend systems, '
'distributed computing, and cloud-native architectures. Led a team of 8 engineers '
'delivering a real-time data pipeline processing 2M+ events/sec.',
body_style
))
# Experience
story.extend(section_header('WORK EXPERIENCE'))
story.extend(experience_entry(
'Senior Software Engineer', 'Tech Company Inc.', 'Jan 2022 - Present', 'Shanghai',
[
'Designed and deployed a microservices architecture serving 10M daily active users',
'Reduced API latency by 40% through query optimisation and caching strategies',
'Mentored 3 junior engineers; established code review standards adopted team-wide',
]
))
story.extend(experience_entry(
'Software Engineer', 'Startup Co.', 'Jul 2019 - Dec 2021', 'Beijing',
[
'Built real-time recommendation engine using collaborative filtering (CTR +25%)',
'Implemented CI/CD pipeline reducing deployment time from 2 hours to 15 minutes',
]
))
# Education
story.extend(section_header('EDUCATION'))
story.extend(education_entry(
'M.Sc. Computer Science', 'Tsinghua University', '2017 - 2019',
'GPA: 3.8/4.0 | Thesis: Distributed Graph Processing on Heterogeneous Clusters'
))
story.extend(education_entry(
'B.Eng. Software Engineering', 'Zhejiang University', '2013 - 2017'
))
# Skills
story.extend(section_header('SKILLS'))
story.extend(skills_row([
('Languages', 'Python, Java, Go, SQL, TypeScript'),
('Frameworks', 'Spring Boot, FastAPI, React, Kubernetes, Kafka'),
('Tools', 'Git, Docker, Terraform, AWS (EC2/S3/Lambda), PostgreSQL, Redis'),
]))
doc.build(story)
Resume Checklist
- 1 page (unless user says otherwise)
- No cover page, no TOC
- Tight margins (1.5cm all sides)
- Name prominent at top (22-26pt)
- Contact info single line, centered
- Section headers with consistent separator style
- Bullets concise - start with action verbs
- Quantified achievements (%, $, count)
- No photos, no icons, no colour blocks (ATS-safe)
- Font: only registered fonts (FreeSerif / Noto Sans SC)
- ⚠️ Minimum font size 12px (9pt) - no text smaller than this anywhere
- Line breaks are language-aware - no mid-word English breaks, no CJK punctuation orphans, no date range splits
- Page fill ≥85% - no large blank area at bottom. If sparse, increase spacing/leading/font size adaptively