hey_screen
Approve or reject email senders by address to control where their messages land. Approving routes future emails to imbox, feed, or paper trail. Rejecting blocks future emails without spam flagging.
Instructions
Approve or reject a sender by email address. Approve: routes the sender's current and future emails into the chosen destination (defaults to imbox). Reject (a.k.a. screen out): blocks the sender from sending you further emails — works for both pending screener entries AND already-approved senders (falls back to the contact-page 'Screened Out' affordance via /contacts/{id}/clearance). Reject does NOT flag emails as spam; existing emails are left untouched. Reversible from the Hey UI by visiting the contact page; not yet exposed via MCP. Returns {success, error?}. Use hey_list_screener to see pending senders, or hey_screen_by_id for clearance IDs.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| sender_email | Yes | The sender's email address | |
| action | Yes | approve: allow this sender's emails through (routed to `destination`). reject: block future emails from this sender. Does not flag as spam, does not move existing emails. Reversible via the Hey UI's contact page. | |
| destination | No | Where future emails from this sender land when approved: imbox (default, important mail), feed (newsletters/updates), paper_trail (receipts/automated). Ignored when action is reject. |
Implementation Reference
- src/index.ts:1566-1614 (handler)MCP tool call handler for 'hey_screen'. It validates sender_email, action (approve/reject), and optional destination, then delegates to either screenIn() or screenOut() from src/tools/organise.ts.
case "hey_screen": { const senderEmail = validateEmail(args?.sender_email) const action = args?.action as string const destination = args?.destination as | "imbox" | "feed" | "paper_trail" | undefined if (!senderEmail) { return { content: [ { type: "text", text: "Error: sender_email is required and must be a valid email", }, ], isError: true, } } if (!action || !["approve", "reject"].includes(action)) { return { content: [ { type: "text", text: "Error: action is required (approve or reject)", }, ], isError: true, } } if ( destination && !["imbox", "feed", "paper_trail"].includes(destination) ) { return { content: [ { type: "text", text: "Error: destination must be one of imbox, feed, paper_trail", }, ], isError: true, } } result = action === "approve" ? await screenIn(senderEmail, destination) : await screenOut(senderEmail) break - src/index.ts:711-742 (schema)Tool schema / registration for 'hey_screen'. Defines the tool name, annotations, description, and input schema (sender_email, action, destination).
name: "hey_screen", annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true, }, description: "Approve or reject a sender by email address. Approve: routes the sender's current and future emails into the chosen destination (defaults to imbox). Reject (a.k.a. screen out): blocks the sender from sending you further emails — works for both pending screener entries AND already-approved senders (falls back to the contact-page 'Screened Out' affordance via /contacts/{id}/clearance). Reject does NOT flag emails as spam; existing emails are left untouched. Reversible from the Hey UI by visiting the contact page; not yet exposed via MCP. Returns {success, error?}. Use hey_list_screener to see pending senders, or hey_screen_by_id for clearance IDs.", inputSchema: { type: "object" as const, properties: { sender_email: { type: "string", description: "The sender's email address", }, action: { type: "string", enum: ["approve", "reject"], description: "approve: allow this sender's emails through (routed to `destination`). reject: block future emails from this sender. Does not flag as spam, does not move existing emails. Reversible via the Hey UI's contact page.", }, destination: { type: "string", enum: ["imbox", "feed", "paper_trail"], description: "Where future emails from this sender land when approved: imbox (default, important mail), feed (newsletters/updates), paper_trail (receipts/automated). Ignored when action is reject.", }, }, required: ["sender_email", "action"], }, }, - src/tools/organise.ts:249-270 (handler)The screenIn() helper function — resolves a sender email to a clearance ID by parsing the screener page, then delegates to screenInById() to approve the sender.
export async function screenIn( senderEmail: string, destination?: MoveDestination, ): Promise<OrganiseResult> { if (!senderEmail) { return { success: false, error: "Sender email is required" } } try { const clearanceId = await findClearanceIdByEmail(senderEmail) if (!clearanceId) { return { success: false, error: `Sender ${senderEmail} not found in screener. Use hey_list_screener to see pending senders.`, } } return screenInById(clearanceId, destination) } catch (err) { return { success: false, error: toUserError(err) } } } - src/tools/organise.ts:315-341 (handler)The screenOut() helper function — tries to reject via the screener (clearance page) first; if the sender is already approved, falls back to the contact-page 'Screened Out' affordance via /contacts/{id}/clearance.
export async function screenOut(senderEmail: string): Promise<OrganiseResult> { if (!senderEmail) { return { success: false, error: "Sender email is required" } } try { const clearanceId = await findClearanceIdByEmail(senderEmail) if (clearanceId) { return screenOutById(clearanceId) } // Sender isn't pending in the screener; they've already been approved. // Fall back to the contact-page "Screened Out" affordance, which blocks // future emails without flagging existing ones as spam. const contactId = await findContactIdByEmail(senderEmail) if (contactId) { return screenOutByContactId(contactId) } return { success: false, error: `Sender ${senderEmail} not found in screener or contacts. They may not exist in your Hey account.`, } } catch (err) { return { success: false, error: toUserError(err) } } } - src/tools/organise.ts:275-313 (helper)Helper that fetches the /clearances screener page and finds a clearance ID by matching on the sender email address.
async function findClearanceIdByEmail( senderEmail: string, ): Promise<string | null> { const html = await heyClient.fetchHtml("/clearances") const root = parseHtml(html) const forms = root.querySelectorAll("form[action*='/clearances/']") for (const form of forms) { const formHtml = form.toString().toLowerCase() if (formHtml.includes(senderEmail.toLowerCase())) { const action = form.getAttribute("action") const match = action?.match(/\/clearances\/(\d+)/) if (match) { return match[1] } } } const articles = root.querySelectorAll( "article, section, [data-clearance-id]", ) for (const article of articles) { const articleText = article.text.toLowerCase() if (articleText.includes(senderEmail.toLowerCase())) { const form = article.querySelector("form[action*='/clearances/']") const action = form?.getAttribute("action") const match = action?.match(/\/clearances\/(\d+)/) if (match) { return match[1] } const clearanceId = article.getAttribute("data-clearance-id") if (clearanceId) { return clearanceId } } } return null }