← Back to Skills Marketplace
chenyuan99

Job Application Manager

by Yuan Chen · GitHub ↗ · v1.0.2 · MIT-0
cross-platform ⚠ pending
146
Downloads
0
Stars
1
Active Installs
3
Versions
Install in OpenClaw
/install job-application-manager
Description
Sync job application statuses from Gmail into Notion or a local SQLite database
README (SKILL.md)

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):

  1. Tracker backendIntegrations > Tracker Backend

    • If blank: ask the user — "Do you use Notion or would you prefer a local SQLite file?"
    • Set to notion or sqlite
  2. 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>
  3. 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')));"
  4. Gmail label IDsIntegrations > Gmail Labels table

    • If missing: run mcp__claude_ai_Gmail__list_labels, show the list, ask the user which labels are job-related, fill in the table in profile.md.
  5. Career emailPersonal 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>.com or 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
How to Use
  1. Make sure OpenClaw is installed (local or Docker)
  2. Run the install command in chat: /install job-application-manager
  3. After installation, invoke the skill by name or use /job-application-manager
  4. Provide required inputs per the skill's parameter spec and get structured output
Version History
v1.0.2
Align skill versions with CLI v0.1.9
v1.0.1
Add tracker add/update/get; application-manager now uses CLI instead of raw sqlite3
v1.0.0
Application Manager 1.0.0 - Initial release: sync job application statuses from Gmail into Notion or a local SQLite database. - Supports setup flows for Notion or SQLite backends, with prompts to collect user info as needed. - Maps Gmail senders and labels to companies and uses status mapping from common email phrases. - Automates deduplication and updating/creating application records in your chosen tracker. - Provides a structured workflow from reading the user profile to reporting sync results. - Includes detailed configuration instructions for first-time setup and backend-specific options.
Metadata
Slug job-application-manager
Version 1.0.2
License MIT-0
All-time Installs 1
Active Installs 1
Total Versions 3
Frequently Asked Questions

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.

💬 Comments