Our team consists of numerous students from diverse backgrounds, such as EE, BME, Applied Mathematics, and Biology. However, despite our diversity, we were all interested in seeking internships this summer. Naturally, the topic of cold emailing came up, as well as our past successes and failures with the technique. The biggest issue, by far, was the time sink that came with this method. As a result, we wanted to design a hackathon project aimed at automating or partially automating the cold emailing pipeline.
Nudge takes in user data to build a profile of the user, identify desired range of jobs/interns/research, and then uses a scraper to scour the internet for local firms who have a team overlapping with your interests. From there, Nudge contacts the head of the team, crafting a hyper-personalized letter to them based on their publicly visible information. Users have the ultimate authority on the email at all times, and can choose to either decline or send an email.
Nudge is a Next.js (TypeScript) web app on Vercel, with Auth0 for accounts, Postgres (Neon) for storage, the Gmail API for sending and reading replies, Tavily for web search and page extraction, and Gemini as the language model.
The core is a staged AI pipeline where each step returns JSON checked against a zod schema:
Profile: the resume, LinkedIn PDF and any other documents become a structured profile. Style: writing samples become a “style card” (greeting, sign-off, sentence and email length, tone). The samples themselves are thrown away. Plan and search: the LLM writes search queries from the profile, target roles and location. Tavily runs them, and job boards and social sites are filtered out. Rank: a TF-IDF prefilter with no API calls narrows the results. One batched LLM call then scores fit from 0–100 with a reason. Research: for each organization, Nudge reads its own team, about and contact pages. The LLM picks the most relevant person plus a specific detail about their work to mention. Emails are pulled straight from the page, including obfuscated ones like name [at] ufl [dot] edu. If the person’s address isn’t listed, Nudge learns the organization’s format (first.last@, flast@, …) from other people on the site. Draft: an email in the user’s voice that references that detail. Verify: the LLM lists every claim the draft makes about the sender and checks each one against the resume. Code checks catch leftover placeholders, bad length, clichés, and drafts that don’t match the user’s style.
A background worker sends approved emails under a daily cap. It reads replies in Nudge’s threads and labels each one: interested, needs your reply, not interested, or out of office. Follow-ups go out in the same Gmail thread.
We wrote 51 tests, including the real SQL running against an in-process Postgres, and an offline eval harness that scores contact accuracy, email accuracy and fact-check pass rate for any model.
Free-tier limits: Gemini’s free tier gave us 20 requests a day. We wrote one OpenAI-compatible client, so we could develop on Qwen and switch to Gemini with an env var. We also cache every response in Postgres and batch ranking into a single call. A full demo run costs about a dozen requests, and re-runs cost zero.
Finding the right person without scraping LinkedIn: LinkedIn blocks scrapers and prohibits it. We switched to each organization’s own public pages. The hard part was emails: the LLM sometimes “remembered” addresses that weren’t on the page. We fixed this by only accepting addresses that actually appear in the page text and labeling every lead’s address as found, inferred, general inbox or none. Keeping the AI honest: early drafts embellished experience. That’s why every draft is fact-checked claim by claim against the resume before it can be sent. Reply detection: our out-of-office check was reading the quoted original email at the bottom of replies, so a real reply could get labeled “out of office.” A test caught it, and we now strip quoted history before classifying. Dropping the Chrome extension: we planned one, but Chrome kills idle background scripts and an extension only runs while the browser is open. We moved automation to a server-side worker so it runs with the laptop closed. Gmail threading: follow-ups need Gmail’s thread ID and the right reply headers, or they show up as new emails in the recipient’s inbox.
A full pipeline that runs end to end: profile, discovery, research, draft, verification, send, reply handling and follow-ups.
The fact-check step. Every draft shows which claims are backed by your resume and which aren’t. Finding real contacts and addresses from public organization sites, with honest confidence labels. A clear automation boundary. Auto-send only sends verified drafts with a real address, under a daily cap, and Nudge never answers a person on your behalf. 51 tests and a repeatable eval harness, so we can compare models and prompts with numbers instead of vibes.
Verify inferred addresses before sending (SMTP or a verification API) to cut bounces.
Real location radius using geocoding instead of query planning. Learn from outcomes: which hooks, lengths and organization types get replies, fed back into ranking and drafting. Gmail push notifications instead of polling, for instant “Needs you” alerts. Google OAuth verification so anyone can use it, plus Outlook support. An optional lightweight Chrome extension for notifications only.
Gemini: strong at structured JSON output and fast on our extraction and drafting steps. The OpenAI-compatible endpoint made it a drop-in swap. The 20-requests/day free tier was the main constraint; we designed around it with caching and batching, but a higher hackathon quota would have let us iterate on prompts against Gemini directly.
Auth0: the Next.js SDK took minutes to set up. Login, logout and sessions just worked, which let us skip building auth. Tavily: search plus page extraction in one API was exactly what an agent needs, and the free credits were plenty. Vercel: zero-config deploys for Next.js. The once-a-day cron limit on Hobby meant using an outside scheduler for frequent jobs. Gmail API: powerful, but threading and OAuth testing mode take some reading to get right.
Built With
- auth0
- css
- gemini
- gmail-api
- google-oauth
- llm
- neon
- nextjs
- node.js
- openrouter
- pglite
- postgresql
- qwen
- rag
- react
- tavily
- typescript
- vercel
- vitest
- zod
Log in or sign up for Devpost to join the conversation.