Skip to content

Email Codes

Sameer Nagar edited this page Oct 11, 2026 · 3 revisions

Email codes

Greenhouse often asks for an 8-character security code that it emails right after the first submit. With several tabs and appliers in flight, several jobs can wait at once, sometimes two at the same company. This page explains how the engine gets each job its own code.

Protocol

sequenceDiagram
    participant J as Job (tab)
    participant FS as workspace/codes
    participant M as Matcher (engine codes)
    participant G as Inbox
    J->>FS: write <board>_<id>.wait (company, form URL)
    G-->>M: codes [{company, code, when}] (Gmail tab script or IMAP)
    M->>FS: <slug>.txt = code; append code to <slug>.tried
    FS-->>J: poll every 1 s (round-robin wait, other tabs keep working)
    J->>J: type code, submit
    alt code bounced
      J->>FS: rewrite .wait (re-wait)
      M->>FS: next unused code, never one in .tried
    end
Loading

Matching rules (engine/apply/codes.py)

  • Same company only. The email must name the job's company (normalized compare), so a code is never typed into another company's form.
  • Fresh only. The email must arrive after the job started waiting, with a 3-minute margin for round-robin delay between the submit click and the .wait file. After a bounce the window widens by 10 minutes.
  • One code per job. The oldest waiter gets the oldest unused code, and no code is assigned to two jobs in one pass.
  • Never resend a bounced code. <slug>.tried remembers codes already sent to that job.

Why the last two rules exist: Gmail threads same-subject emails, so two applications to one company can show both codes in one conversation row with one timestamp, and arrival order alone picked the wrong pairing once. The tab script regenCodes now reads every code in each thread through Gmail's print view.

Sources of codes

Mode How Needs
Gmail tab engine/discovery/gmail_codes.js (or the thread-aware regenCodes) → python -m engine codes '<json>' a signed-in Gmail tab
Hands-free python -m engine codes --watch: read-only IMAP (BODY.PEEK, never marks or moves mail), one SEARCH per poll, only while a job waits Gmail app password in .env
MCP submit_codes tool (REGEN_MCP_WRITE=1) an MCP client

Cost: O(waiting × codes) per poll, both tiny. One IMAP login per watch session.

Related: Apply engine · Running the engine · Configuration

Hands-free without an app password: the code relay (v0.12)

  1. python -m engine relay (listens on 127.0.0.1:8799 only).
  2. Open https://mail.google.com/mail/u/0/feed/atom/ (a tiny XML page; the full Gmail UI freezes background tabs) and paste engine/discovery/gmail_relay.js there.

Every 20 s the tab fetches Gmail's Atom feed (unread inbox; one request, no UI, so the tab never freezes the way the search-results UI does after long scripts). It posts [{company, code, iso}] as text/plain, a simple request with no CORS preflight. The relay turns the send time into local HH:MM and calls codes.match(). A code is written only for a job that is waiting, whose company matches, and that started waiting before the code was sent. The relay accepts only short {company, code, iso} records and never reads anything else from the mailbox.

Clone this wiki locally