Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Intern8n

An n8n agent that finds and ranks internships automatically — so I don't have to search every week.


Project Overview

Intern8n automates internship hunting for a final-year student workflow. It searches Google Jobs via SerpApi, filters for actual internships, deduplicates against a Google Sheet of seen listings, ranks the remainder with an LLM against the requested role, and emails the top 5 matches. Two triggers feed the same pipeline: an on-demand chat trigger and a weekly schedule trigger that makes it a true agent — it runs even without manual input.


Why This Project?

I was already searching for internships manually every week. The assignment was to build an agent for a task I already do by hand, so I picked this one. Keeping it fully free-tier (SerpApi, Groq, Gemini, Google Sheets, Gmail via n8n) was a constraint from the start.


Screenshots

n8n workflow — Schedule + Chat triggers → SerpApi → dedup → LLM rank → email

Workflow in the n8n editor: two triggers converge on the same pipeline (screenshot: intern-n8n.png).


Architecture

┌──────────────────┐   ┌──────────────┐   ┌─────────────┐   ┌──────────────┐   ┌────────────┐
│ Schedule Trigger │──▶│  Edit Fields │──▶│ Google Jobs │──▶│    code1     │──▶│  Google    │
│  (weekly, Tue)   │   │ (role input) │   │ (SerpApi)   │   │  (filter)    │   │  Sheets    │
└──────────────────┘   └──────────────┘   └─────────────┘   └─────────────┘   │  (read)    │
        ▲                                                                 └──────────────┘
        │                                                                        │
┌──────────────────┐                                                             ▼
│ When chat message│                                                    ┌──────────────┐
│    received      │──────────────────────────────────────────────────▶│ dedup-filter │
└──────────────────┘                                                    │ (vs. Sheet)  │
                                                                        └──────────────┘
                                                                              │
                                                                              ▼
                                                                        ┌────────┐
                                                                        │   If   │── false ──▶ Code in JavaScript ──▶ Email: No New Matches
                                                                        │jobs>0? │              (no new listings)
                                                                        └────────┘
                                                                              │ true
                                                                              ▼
                                                                    ┌────────────────┐
                                                                    │ Basic LLM Chain│── Success ──▶ code2 ──┬──▶ Email: Weekly Digest
                                                                    │ Groq → Gemini  │              (rank/    │     (up to 5)
                                                                    │  (fallback)    │              format)   └──▶ Split Out ──▶ Mark as seen (Sheet)
                                                                    └────────────────┘
                                                                            │ Error
                                                                            ▼
                                                                    LLM Failure Handler ──▶ Email: Ranking Failed (raw listings)

Flow:

  1. Schedule Trigger (weekly) or When chat message received provides chatInput (e.g., "AI/ML Engineer")
  2. Google_jobs search (SerpApi) fetches Google Jobs listings
  3. code1 filters for internships only; generates stable job_id and keeps apply_options/share_link
  4. Get row(s) in sheet + dedup-filter removes jobs already in the Sheet
  5. If branches: no new jobs → email "No new matches"; has jobs → Basic LLM Chain
  6. Basic LLM Chain (Groq openai/gpt-oss-120b → fallback Gemini) scores/ranks top 5; on error → LLM Failure Handler emails raw listings
  7. code2 builds markdown, Email — Weekly Internship Digest sends HTML, Split Out + Mark as seen appends job_ids to Sheet

Features

  • Two triggers, one pipeline — on-demand chat search + autonomous weekly agent run (Tuesdays, weekly interval)
  • Internship-only filtering — code node drops full-time/non-intern listings before LLM sees them
  • Deduplication — Google Sheets job_id store; never emails the same listing twice
  • LLM ranking with guardrails — prompt instructs "copy job_id exactly", top 5 only, score 1-10; application links are pulled from SerpApi data in code, LLM never touches URLs (fixes earlier hallucinated-URL bug)
  • Failure handlingBasic LLM Chain set to continueErrorOutput; error branch builds raw HTML via LLM Failure Handler and emails "ranking failed" so runs never fail silently; tested by deliberately disconnecting both LLM providers
  • Free-tier only — SerpApi free tier, Groq + Gemini free tiers, n8n self-hosted

Tech Stack

Layer Technology
Orchestration n8n (workflow flyrank-internship-agent-v2.json)
Jobs API SerpApi — google_jobs engine
Filtering / Dedup n8n Code nodes (JavaScript), Google Sheets
Ranking Basic LLM Chain + Groq (openai/gpt-oss-120b) + Google Gemini (fallback)
Notifications Gmail (3 templates: Digest / No Matches / Ranking Failed)
State Google Sheets (job_id column, appendOrUpdate)

Quick Start

Prerequisites

  • n8n instance (self-hosted or n8n Cloud)
  • SerpApi API key
  • Groq API key and/or Google Gemini API key
  • Google Sheets OAuth + a Sheet with a job_id column
  • Gmail OAuth

Import the workflow

# 1. Clone (after you recreate the repo)
git clone https://github.com/NameRectified/intern8n.git
cd intern8n

# 2. In n8n: Workflows → Import from File → select flyrank-internship-agent-v2.json

Configure credentials (in n8n UI)

  1. SerpApi — Credentials → SerpApi → paste key → assign to Google_jobs search
  2. Groq → assign to Groq Chat Model
  3. Google Gemini (PaLM) → assign to Google Gemini Chat Model
  4. Google Sheets OAuth2 → assign to Get row(s) in sheet + Mark as seen; set documentId to your Sheet ID (replace YOUR_GOOGLE_SHEET_ID placeholder)
  5. Gmail OAuth2 → assign to all 3 Email nodes; replace YOUR_EMAIL@gmail.com

Configure triggers

  • Schedule Trigger: default is every week on Tuesday; adjust in the node
  • Edit Fields: default chatInput = "AI/ML Engineer" — used by the schedule path
  • Chat Trigger: send any role string (e.g., ai engineer) to run on demand

Run

  • Manual: click Execute workflow or send a chat message
  • Scheduled: activate the workflow; it emails automatically each week

Project Structure

intern8n/
├── flyrank-internship-agent-v2.json  # n8n workflow export (sanitized placeholders)
├── intern-n8n.png                    # Workflow screenshot (editor)
├── README.md
└── .gitignore

Example Output

Weekly Digest email (success, up to 5):

<h2>Top Internship Matches</h2>
<h3>1. AI/ML Engineer Intern</h3>
<strong>Company:</strong> Example Labs<br>
<strong>Location:</strong> Bengaluru, India<br>
<strong>Posted:</strong> 2 days ago<br>
<strong>Match:</strong> 9/10<br>
<strong>Why:</strong> Direct match for AI/ML Engineer, internship for students...<br>
<strong>Apply:</strong> <a href="...">Apply on LinkedIn</a> <a href="...">Company site</a>

No new listings:

No new internship matches found this week.

Ranking failed (LLM rate-limited / down):

<h2>Internship Ranking Failed</h2>
<p>The AI ranking step failed this week, but new listings were found.</p>
<hr><h3>1. Backend Intern</h3> ... <a href="...">Apply</a>

All ranking-failure, dedup-miss, and success paths still email — no silent failures.


Design Decisions

Why SerpApi Google Jobs? Generous free tier; direct apply_options + share_link in the response. Alternative job APIs were paid or lacked India coverage.

Why pull apply links in code instead of from the LLM? Early version let the LLM return URLs — it hallucinated real jobs with wrong links. Fix: keep application_links from SerpApi in code1, forbid LLM from emitting URLs in the prompt ("Do not include any application URLs", "Return only job_id, score, reason"), re-attach links in code2 by job_id. Verified code1/code2 always use SerpApi links verbatim.

Why Groq + Gemini fallback + failure handler? Sticking to free tiers means rate limits. Basic LLM Chain primary = Groq, fallback = Gemini, onError: continueErrorOutput routes to LLM Failure Handler. Deliberately disconnecting both providers emails the raw listings instead of failing silently.

Why Google Sheets for dedup? Free, visible, and n8n has a native node. job_id is a stable base64 of title|company|location (24 chars); dedup-filter set-diffs against all rows. Mark as seen uses appendOrUpdate on job_id.

Why two triggers? Chat trigger covers on-demand searches; Schedule Trigger (weekly) is what makes it an agent per the brief — autonomous execution without input, emailing results.


Future Improvements

  • Deploy for real scheduling (free host for n8n — e.g., Render/Fly/Hugging Face Space with cron) and validate weekly runs in production
  • Prove failure handler under real rate-limit conditions (not just deliberate disconnect test)
  • Add role/location parameterization via Sheet or chat config instead of hard-coded Edit Fields
  • Add tests for code1/dedup-filter/code2 logic outside n8n
  • Add Sheets cleanup/retention for old job_ids

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors