srbhr/Resume-Matcher · error · Error
Failed to generate outreach message (status ${res.status}):
Error message
Failed to generate outreach message (status ${res.status}): ${text} What it means
generateOutreachMessage calls POST /resumes/{id}/generate-outreach and throws this Error whenever the backend responds with a non-OK HTTP status. The response body text (often a JSON error detail) is embedded in the message to expose the server-side reason. This is a client-side guard converting an API failure into a thrown exception for the caller to handle.
Source
Thrown at apps/frontend/lib/api/resume.ts:358
}
/** Generates a cover letter on-demand for a tailored resume */
export async function generateCoverLetter(resumeId: string): Promise<string> {
const res = await apiPost(`/resumes/${encodeURIComponent(resumeId)}/generate-cover-letter`, {});
if (!res.ok) {
const text = await res.text().catch(() => '');
throw new Error(`Failed to generate cover letter (status ${res.status}): ${text}`);
}
const data = await res.json();
return data.content;
}
/** Generates an outreach message on-demand for a tailored resume */
export async function generateOutreachMessage(resumeId: string): Promise<string> {
const res = await apiPost(`/resumes/${encodeURIComponent(resumeId)}/generate-outreach`, {});
if (!res.ok) {
const text = await res.text().catch(() => '');
throw new Error(`Failed to generate outreach message (status ${res.status}): ${text}`);
}
const data = await res.json();
return data.content;
}
/** Generates interview preparation on-demand for a tailored resume */
export async function generateInterviewPrep(resumeId: string): Promise<InterviewPrepData> {
const res = await apiPost(`/resumes/${encodeURIComponent(resumeId)}/generate-interview-prep`, {});
if (!res.ok) {
const text = await res.text().catch(() => '');
throw new Error(`Failed to generate interview preparation (status ${res.status}): ${text}`);
}
const data = (await res.json()) as { interview_prep: InterviewPrepData };
return data.interview_prep;
}
/** Retries AI processing for a failed resume */
export async function retryProcessing(resumeId: string): Promise<ResumeUploadResponse> {View on GitHub (pinned to 116f9cc3b0)
Solutions
- Check the embedded status/text in the message to identify the server reason (401/404/500 etc.)
- Verify the resumeId exists and belongs to the authenticated user
- Ensure the resume finished processing successfully before requesting outreach generation
- Re-authenticate the frontend session if status is 401/403
- Retry later if the backend AI provider is rate-limited or down (5xx)
- Inspect backend logs for the generate-outreach handler failure
Example fix
// before
data.content = await generateOutreachMessage(resumeId);
// after
try {
data.content = await generateOutreachMessage(resumeId);
} catch (e) {
showToast('Could not generate outreach message. Please retry once the resume is ready.');
data.content = null;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check the resume is ready before calling
const status = await getResumeStatus(resumeId);
if (status.state !== 'completed') {
throw new Error(`Resume ${resumeId} is not ready for outreach generation (state: ${status.state})`);
}
if (!resumeId || resumeId.trim() === '') {
throw new Error('resumeId is required');
} Type guard
function isHttpErrorWithStatus(e: unknown): e is Error & { status?: number } {
const m = e instanceof Error ? e.message.match(/status (\d{3})/) : null;
return e instanceof Error && m !== null;
} Try / catch
try {
const content = await generateOutreachMessage(resumeId);
} catch (e) {
if (isHttpErrorWithStatus(e) && e.message.includes('status 401')) {
redirectToLogin();
} else {
showError('Outreach generation failed. Verify the resume is processed, then retry.');
}
} Prevention
- Gate outreach generation behind resume status === 'completed'
- Handle 401 globally by refreshing the session before API calls
- Retry 5xx with backoff; never retry 4xx
- Show the embedded status/text to the user for diagnosis
- Disable the outreach button while the resume is still processing
When it happens
Trigger: Any non-2xx response from the generate-outreach endpoint: resume not found (404), AI generation failure or upstream LLM timeout (500/502/504), unauthenticated/expired session (401), or invalid resume id causing a backend validation rejection.
Common situations: Developers hit this when a resume is still processing or failed AI enrichment and outreach generation is requested, when the session token expired, when the backend LLM provider is down/rate-limited, or when a stale resumeId is used from a deleted record.
Related errors
- Failed to generate interview preparation (status ${res.statu
- Failed to fetch job description (status ${res.status}): ${te
- Failed to retry processing (status ${res.status}): ${text}
- ${fallback} (status ${res.status}).
- Failed to load LLM config (status ${res.status}).
AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28).
Data as JSON: /api/errors/7dbf8a0187511a04.
Report an issue: GitHub.