amruthpillai/reactive-resume · error · ORPCError

NOT_FOUND

NOT_FOUND

Error message

No cover letter found for this resume

What it means

createResumePdfDownload() throws NOT_FOUND when target is 'cover-letter' but resumeHasCoverLetter(data) is false. The cover letter is an optional resume section; exporting it requires the resume to actually contain one. The check runs after loading the resume but before invoking the PDF renderer.

Source

Thrown at packages/api/src/features/resume/export.ts:28

export {
	createResumePdfDownloadUrl,
	MAX_PDF_DOWNLOAD_URL_TTL_SECONDS,
	verifyResumePdfDownloadToken,
} from "./pdf-download-url";

type CreateResumePdfDownloadInput = {
	id: string;
	userId: string;
	target?: ResumeExportTarget;
};

export async function createResumePdfDownload(input: CreateResumePdfDownloadInput) {
	const resume = await resumeService.getById({ id: input.id, userId: input.userId });
	const data = parseStoredResumeData(resume.data);
	const target = input.target ?? "resume";
	if (target === "cover-letter" && !resumeHasCoverLetter(data)) {
		throw new ORPCError("NOT_FOUND", { message: "No cover letter found for this resume" });
	}

	const filename = generateFilename(target === "cover-letter" ? `${resume.name} Cover Letter` : resume.name, "pdf");

	try {
		// Lazy-load the PDF renderer (@reactive-resume/pdf → @react-pdf/renderer +
		// phosphor-icons-react-pdf, ~10.6k icon modules) only when a PDF is actually
		// exported, instead of at server boot. Slashes cold-start file I/O on
		// constrained/slow-disk hosts. See fork perf/lazy-load-pdf.
		const { createResumePdfFile } = await import("@reactive-resume/pdf/server");
		const body = await createResumePdfFile({ data: getResumeExportData(data, target), filename });

		return {
			headers: {
				"content-disposition": `attachment; filename="${filename}"`,
			},
			body,
		};

View on GitHub (pinned to 3a5b12e2a4)

Solutions

  1. Call resumeHasCoverLetter(data) (or check data.sections.summary.coverLetter) before offering the cover-letter export.
  2. Gate the UI option behind presence of the section.
  3. Default target to 'resume' when no cover letter exists.

Example fix

// before
downloadResumePdf({ id, target: 'cover-letter' });
// after
const has = resumeHasCoverLetter(resumeData);
downloadResumePdf({ id, target: has ? 'cover-letter' : 'resume' });
Defensive patterns

Strategy: validation

Validate before calling

import { resumeHasCoverLetter } from '@reactive-resume/resume/export-sections';
const target = input.target === 'cover-letter' && resumeHasCoverLetter(data) ? 'cover-letter' : 'resume';

Type guard

function hasCoverLetter(data): boolean {
  return Boolean(data?.sections?.summary?.coverLetter);
}

Try / catch

try {
  await downloadResumePdf({ id, target: 'cover-letter' });
} catch (e) {
  if (e.code === 'NOT_FOUND') { /* fall back to resume export */ await downloadResumePdf({ id, target: 'resume' }); }
  else throw e;
}

Prevention

When it happens

Trigger: GET /resumes/{id}/pdf?target=cover-letter on a resume whose data has no cover-letter section (or it is empty).

Common situations: A download menu that always offers 'cover letter' regardless of whether the user added one; a fresh template that doesn't include the section; a resume where the section was deleted.

Related errors


AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12). Data as JSON: /api/errors/d3d933cc245c09d9. Report an issue: GitHub.