/install job-application-manager
Application Manager
When to Use This Skill
Trigger when the user asks to:
- Sync / refresh application emails from Gmail into their tracker
- Update the status of one or more specific applications
- Add new applications discovered in Gmail
- Check what emails arrived from a specific company
- Keywords: "update my application", "sync applications", "add to notion", "check my tracker", "update my sqlite tracker"
Setup (first-time use)
Always read profile.md first. It contains the user's tracker backend choice,
Notion database ID or SQLite path, Gmail label IDs, and career email.
Resolve these fields before continuing (collect from user + write back to profile.md):
-
Tracker backend —
Integrations > Tracker Backend- If blank: ask the user — "Do you use Notion or would you prefer a local SQLite file?"
- Set to
notionorsqlite
-
If notion — resolve
Integrations > Notion > Career tracker database ID- If missing: ask user to open their Notion career tracker and copy the URL.
The ID is the UUID in
notion.so/\x3Cworkspace>/\x3CDATABASE_ID>?v=... - Derive:
NOTION_DB_ID,COLLECTION_URL = collection://\x3CNOTION_DB_ID>
- If missing: ask user to open their Notion career tracker and copy the URL.
The ID is the UUID in
-
If sqlite — resolve
Integrations > Tracker Backend > SQLite DB path- Default:
~/.offerplus/applications.db - On first use, initialize the DB:
swelist tracker init --db \x3Cpath>Or directly:sqlite3 \x3Cpath> "CREATE TABLE IF NOT EXISTS applications (name TEXT PRIMARY KEY, status TEXT NOT NULL, job_id TEXT, applied_on TEXT, notes TEXT, updated_at TEXT DEFAULT (datetime('now')));"
- Default:
-
Gmail label IDs —
Integrations > Gmail Labelstable- If missing: run
mcp__claude_ai_Gmail__list_labels, show the list, ask the user which labels are job-related, fill in the table inprofile.md.
- If missing: run
-
Career email —
Personal Info > Career email- If missing: ask the user which email address receives job application emails.
Config: Company → Gmail
Populate from the user's own labels (mcp__claude_ai_Gmail__list_labels):
| Company key | Gmail sender pattern | Gmail label ID | Label name |
|---|---|---|---|
amazon |
[email protected] |
(run list_labels) | e.g. amazon |
linkedin |
[email protected] |
(run list_labels) | e.g. linkedin |
google |
@google.com |
— | — |
meta |
@meta.com |
— | — |
_any_ |
— | — | — |
Add rows for any other companies the user has labeled. If no labels exist, rely on sender pattern alone.
Config: Status Mapping
Map email signals → status value (pick the first match):
| Priority | Signal (case-insensitive) | Status |
|---|---|---|
| 1 | "offer" OR "congratulations" OR "pleased to inform" OR "we'd like to extend" | Done |
| 2 | "interview" OR "move forward" OR "next steps" OR "schedule" OR "hiring manager" | In progress |
| 3 | "unable to move forward" OR "not selected" OR "no longer considering" OR "other candidates" OR "position has been filled" | Rejected |
| 4 | "application received" OR "thank you for applying" OR "keep track" OR "under review" | In progress |
| 5 | (no email found, only a job listing) | Not started |
If multiple threads exist for the same role, use the most recent email's status.
Config: Notion Database
(skip if tracker_backend is sqlite)
| Field | Value |
|---|---|
| Collection URL | collection://\x3CNOTION_DB_ID> |
| Parent ID (for creates) | \x3CNOTION_DB_ID> |
Expected schema:
| Property | Type | Allowed values |
|---|---|---|
Name |
title | "Company — Role Title" |
status |
select | Not started, In progress, Rejected, Done |
Tags |
multi-select | Only use values already in the options list |
Config: SQLite Schema
(skip if tracker_backend is notion)
CREATE TABLE IF NOT EXISTS applications (
name TEXT PRIMARY KEY, -- "Company — Role Title"
status TEXT NOT NULL, -- Not started | In progress | Rejected | Done
job_id TEXT,
applied_on TEXT, -- ISO date YYYY-MM-DD
notes TEXT,
updated_at TEXT DEFAULT (datetime('now'))
);
Workflow
INPUT: company (optional), date_range (default: newer_than:6m)
STEP 0 Load profile
Read profile.md → extract tracker_backend, CAREER_EMAIL, label table
IF tracker_backend == "notion": resolve NOTION_DB_ID, COLLECTION_URL
IF tracker_backend == "sqlite": resolve DB_PATH; run init if DB does not exist
IF any required field blank: collect from user → write to profile.md → continue
IF company given AND not in label table:
run list_labels → confirm with user → append to profile.md label table
STEP 1 Search Gmail
IF company given:
query = build_query(company) # see Query Builder below
ELSE:
query = 'subject:"application" OR subject:"your application" OR subject:"interview" newer_than:6m'
threads = gmail_search(query, max_results=20)
FOR each thread WHERE snippet is ambiguous:
fetch full thread via gmail_get_thread(thread_id)
STEP 2 Parse threads → applications[]
FOR each thread:
company_name = extract_company(thread)
role_title = extract_role(thread)
status = map_status(thread) # use Status Mapping table above
date = most_recent_message_date(thread)
job_id = extract_job_id(thread) # if present in email
page_name = f"{company_name} — {role_title}"
APPEND { page_name, status, date, job_id, thread_id }
STEP 3 Deduplicate
IF tracker_backend == "notion":
FOR each application:
existing = notion_search(query=application.page_name,
data_source_url=COLLECTION_URL)
IF found: set existing_status, action = "update" or "skip"
ELSE: action = "create"
IF tracker_backend == "sqlite":
FOR each application:
result = swelist tracker get "\x3Cpage_name>" --db DB_PATH
IF result is not null: set existing_status, action = "update" or "skip"
ELSE: action = "create"
STEP 4 Apply changes
IF tracker_backend == "notion":
creates → notion_create_pages(batch)
updates → notion_update_page per entry
IF tracker_backend == "sqlite":
FOR each create:
swelist tracker add "\x3Cname>" --status \x3Cstatus> --job-id \x3Cid> --applied-on \x3Cdate> --db DB_PATH
FOR each update:
swelist tracker update "\x3Cname>" --status \x3Cstatus> --db DB_PATH
STEP 5 Report
print summary (see Report Format below)
Query Builder
FUNCTION build_query(company_key, date_range="newer_than:6m"):
cfg = COMPANY_CONFIG[company_key]
parts = []
IF cfg.sender: parts.append(f"from:{cfg.sender}")
IF cfg.label_id: parts.append(f"label:{cfg.label_id}")
subject_terms = 'subject:application OR subject:interview OR subject:offer OR subject:position'
RETURN f'({" OR ".join(parts)}) ({subject_terms}) {date_range}'
Examples:
- Amazon with label →
(from:[email protected] OR label:\x3Clabel_id>) (subject:application OR ...) newer_than:6m - Unknown company → fall back to
from:@\x3Ccompany>.comor company name in subject
Storage API Calls
Notion — Create (batch)
{
"data_source_id": "\x3CNOTION_DB_ID>",
"pages": [
{
"properties": { "Name": "Amazon — SDE, AWS", "status": "In progress" },
"content": "Applied: \x3Cdate>"
}
]
}
Notion — Update (single)
{
"page_id": "\x3Cpage-id>",
"command": "update_properties",
"properties": { "status": "Rejected" },
"content_updates": []
}
Notion — Search (dedup)
{
"query": "Amazon — Software Development Engineer",
"data_source_url": "collection://\x3CNOTION_DB_ID>",
"filters": {}
}
SQLite — Create
swelist tracker add "Amazon — SDE, AWS" \
--status "In progress" \
--job-id 10414382 \
--applied-on 2026-05-17 \
--db \x3CDB_PATH>
SQLite — Update
swelist tracker update "Amazon — SDE, AWS" \
--status "Rejected" \
--db \x3CDB_PATH>
SQLite — Search (dedup)
swelist tracker get "Amazon — SDE, AWS" --db \x3CDB_PATH>
# Returns JSON object if found, null if not found. Exit code 1 when not found.
SQLite — List all
swelist tracker list --db \x3CDB_PATH>
swelist tracker export --format json --db \x3CDB_PATH>
Naming Convention
"{Company} — {Role Title}"
| Good | Bad |
|---|---|
Amazon — Software Development Engineer, AWS |
SDE at Amazon |
Google — Software Engineer II, Early Career |
Google SWE |
Goldman Sachs — Software Engineer - Associate |
Goldman |
Rules:
- Em dash (
—), not hyphen - Company name in title case
- Role title verbatim from the job posting or email subject when possible
Report Format
Synced {N} application(s) on {date} [{backend}: notion|sqlite]
Created:
✦ {Company} — {Role} → {status}
Updated:
↑ {Company} — {Role} → {new_status} (was: {old_status})
Skipped (already up to date):
· {Company} — {Role} → {status}
Errors:
✕ {description of issue}
Edge Cases
| Situation | Handling |
|---|---|
| Multiple threads for same role | Use most recent thread's status |
| Role title not in email | Use job ID or "Role" as placeholder; note it in content/notes |
| Company exists under a different name variant | Search by company name alone first, prompt user to confirm merge |
| Email snippet enough to determine status | Skip full thread fetch to reduce API calls |
Tags value not in Notion options list |
Omit Tags; do not create new options |
| Page name collision (same company, same title, different role) | Add disambiguator: Amazon — SDE, AWS (Job ID 12345) |
| Notion DB ID or schema unknown | Fetch database URL with notion-fetch to inspect schema first |
| SQLite DB does not exist | Run swelist tracker init or the CREATE TABLE statement before Step 3 |
| SQLite DB path missing from profile.md | Use default ~/.offerplus/applications.db; confirm with user |
- Make sure OpenClaw is installed (local or Docker)
- Run the install command in chat:
/install job-application-manager - After installation, invoke the skill by name or use
/job-application-manager - Provide required inputs per the skill's parameter spec and get structured output
What is Job Application Manager?
Sync job application statuses from Gmail into Notion or a local SQLite database. It is an AI Agent Skill for Claude Code / OpenClaw, with 146 downloads so far.
How do I install Job Application Manager?
Run "/install job-application-manager" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.
Is Job Application Manager free?
Yes, Job Application Manager is completely free, licensed under MIT-0. You can download, install and use it at no cost.
Which platforms does Job Application Manager support?
Job Application Manager is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).
Who created Job Application Manager?
It is built and maintained by Yuan Chen (@chenyuan99); the current version is v1.0.2.