request_outreach
Send job invitations to potential candidates in the WonderCV database. Candidates receive branded messages and can choose to join ClawHire to share their profile with you.
Instructions
向数据库候选人(未入驻 ClawHire 的 WonderCV 用户)发送职位邀请。
工作原理:
ClawHire 代表您发送一封品牌化的邮件/微信消息
候选人收到:"【公司名】对您的简历感兴趣,邀请您了解 [职位名称]"
候选人可选择:感兴趣(加入 ClawHire 并开放档案)或不感兴趣
如候选人感兴趣,将自动出现在您的候选人列表中
限制:
每日限额(Alpha: 10次/天, Free: 5次/天)
同一候选人90天内只能联系一次
候选人可随时退订
提示:
建议配合职位使用,说明具体职位而非泛泛邀请
简短的个性化消息可提高回复率
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| session_id | Yes | 会话 ID,从 register_company 获取 | |
| candidate_ref | Yes | 候选人引用ID(从 search_candidates 的 database_candidates 获取) | |
| job_token | Yes | 关联的职位token,用于在邀请邮件中展示职位信息 | |
| message | No | 自定义邀请消息(可选),将附在标准模板后 |
Implementation Reference
- src/tools/request_outreach.ts:52-162 (handler)The main handler function that executes the request_outreach tool logic - validates session, checks quota, calls backend API, and returns response
async execute(input: Input) { // Validate session const session = getSession(input.session_id); if (!session) { return { content: [ { type: 'text', text: JSON.stringify({ error: { code: 'INVALID_SESSION', message: '会话已过期或无效,请重新调用 register_company', }, }, null, 2), }, ], isError: true, }; } // Check outreach quota (preview only) // TODO: Fix race condition under concurrent load (KnownIssues.md #1) const remainingBefore = getRemainingQuota(session, 'outreach_sent'); if (remainingBefore <= 0) { return { content: [ { type: 'text', text: JSON.stringify({ error: { code: 'QUOTA_EXCEEDED', message: `今日 outreach 额度已用完`, }, access: { tier: session.tier, remaining_outreach_today: 0, reset_at: '次日零点', }, }, null, 2), }, ], isError: true, }; } try { const response = await requestOutreach({ session_id: input.session_id, candidate_ref: input.candidate_ref, job_token: input.job_token, message: input.message, }); const { data } = response; // Consume quota only after successful outreach checkAndIncrementUsage(session, 'outreach_sent'); return { content: [ { type: 'text', text: JSON.stringify({ data: { outreach_id: data.outreach_id, status: data.status, message: data.message, note: '候选人尚未加入ClawHire平台。如果候选人感兴趣,将自动出现在您的候选人列表中。', }, access: { tier: session.tier, remaining_outreach_today: getRemainingQuota(session, 'outreach_sent'), }, meta: { session_id: session.session_id, }, next_steps: [ '继续浏览其他数据库候选人', '使用 search_candidates 查看市场候选人(已入驻,可直接查看档案)', '候选人回复后将收到通知', ], }, null, 2), }, ], isError: false, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : '发送邀请失败'; const isRateLimit = errorMessage.includes('rate limit') || errorMessage.includes('cooldown'); return { content: [ { type: 'text', text: JSON.stringify({ error: { code: isRateLimit ? 'OUTREACH_COOLDOWN' : 'OUTREACH_FAILED', message: isRateLimit ? '该候选人近期已收到过邀请,请90天后再试' : errorMessage, }, hint: isRateLimit ? '每位候选人90天内只能收到一次邀请,请尊重候选人隐私' : '请检查 candidate_ref 和 job_token 是否正确', }, null, 2), }, ], isError: true, }; } }, - src/tools/request_outreach.ts:13-29 (schema)Zod input schema defining validation for session_id, candidate_ref, job_token, and optional message fields
const inputSchema = z.object({ session_id: z.string() .describe('会话 ID,从 register_company 获取'), candidate_ref: z.string() .describe('候选人引用ID(从 search_candidates 的 database_candidates 获取)'), job_token: z.string() .describe('关联的职位token,用于在邀请邮件中展示职位信息'), message: z.string() .max(500, '自定义消息不能超过500字') .optional() .describe('自定义邀请消息(可选),将附在标准模板后'), }); type Input = z.infer<typeof inputSchema>; - src/backend-api.ts:346-367 (schema)TypeScript interfaces (RequestOutreachInput, RequestOutreachOutput) and API function for the backend request
export interface RequestOutreachInput { session_id: string; candidate_ref: string; job_token: string; message?: string; } export interface RequestOutreachOutput { outreach_id: string; status: string; message: string; } export async function requestOutreach( input: RequestOutreachInput ): Promise<ApiResponse<RequestOutreachOutput>> { return apiRequest('/candidates/outreach', { method: 'POST', body: input, sessionId: input.session_id, }); } - src/tools/index.ts:34-57 (registration)Tool registration - imports requestOutreachTool and exports it in allTools array for server initialization
import { requestOutreachTool } from './request_outreach.js'; // Export all tools export { registerCompanyTool, postJobTool, listJobsTool, searchCandidatesTool, viewCandidateTool, listApplicationsTool, requestOutreachTool, }; // Tool registry for server initialization // eslint-disable-next-line @typescript-eslint/no-explicit-any export const allTools: Tool<any>[] = [ registerCompanyTool, postJobTool, listJobsTool, searchCandidatesTool, viewCandidateTool, listApplicationsTool, requestOutreachTool, ];