Skip to content

feat(outlook): Microsoft Graph SDK integration — auto-detect interview invites from Outlook + Teams#666

Open
Schlaflied wants to merge 2 commits into
santifer:mainfrom
Schlaflied:feat/scan-outlook-clean
Open

feat(outlook): Microsoft Graph SDK integration — auto-detect interview invites from Outlook + Teams#666
Schlaflied wants to merge 2 commits into
santifer:mainfrom
Schlaflied:feat/scan-outlook-clean

Conversation

@Schlaflied
Copy link
Copy Markdown

@Schlaflied Schlaflied commented May 16, 2026

Closes #664

Summary

Add Microsoft Graph API integration so career-ops can automatically detect interview invitations from Outlook email (personal and enterprise), extract structured data, update the tracker, create interview prep files, and add calendar events — without manual intervention.

Parallel to #660 (Gmail/Google SDK), but targeting Outlook/Exchange users. Most Fortune 500 recruiters (Equinix, EY, Deloitte, etc.) send interview invites via Outlook — this closes the gap for enterprise job seekers.

What this adds

`scan-outlook.mjs` — zero-LLM interview invite scanner via Microsoft Graph REST API

```
node scan-outlook.mjs # scan last 7 days
node scan-outlook.mjs --dry-run # preview without writing files
node scan-outlook.mjs --days 14 # scan last N days
node scan-outlook.mjs --auth # re-run OAuth2 flow
```

Key differentiators vs #660 (Gmail)

Gmail (#660) Outlook (this PR)
Target users Personal Gmail users Outlook personal + Fortune 500 enterprise
Auth Google OAuth2 Microsoft OAuth2 (MSAL-compatible)
Extra npm deps googleapis None (native fetch)
Enterprise support Google Workspace only Azure AD tenant support built-in
Meeting links Zoom/Meet Teams-native + Zoom

What it does

  1. OAuth2 flow on port 3001 (no conflict with Google on 3000), token saved to `calendar/ms-token.json`
  2. Queries `/me/messages` for interview-related subjects (interview, prescreen, phone screen, Teams meeting, etc.)
  3. Extracts: company, role, date/time, Teams/Zoom meeting link
  4. Updates `applications.md` status → `Interview`
  5. Creates prep file skeleton in `interview-prep/{company}-{role}.md`
  6. Dedup by company+role — safe to run multiple times

Config (`config/profile.yml`)

```yaml
microsoft:
client_id: '' # from portal.azure.com app registration
tenant: common # 'common' for personal, tenant ID for enterprise
token_path: calendar/ms-token.json
outlook_scan_days: 7
```

Acceptance Criteria

  • `node scan-outlook.mjs` runs without LLM (zero token cost)
  • Works with personal outlook.com accounts out of the box
  • Enterprise Azure AD tenant configurable via `profile.yml`
  • Does not duplicate entries if run multiple times (dedup by company+role)
  • `ms-token.json` excluded from tracking (`.gitignore`)
  • Port 3001 for OAuth callback (no conflict with Google on 3000)
  • `--dry-run`, `--days N`, `--auth` flags consistent with `scan.mjs` conventions

Related

Summary by CodeRabbit

  • New Features
    • Added Outlook email scanning to detect interview invitations via Microsoft Graph
    • Automatically updates application tracker and generates per-interview preparation notes without overwriting existing files
    • Supports OAuth authorization and command-line options for dry-run and scan scope
    • Appends scan results to a persistent history and prints a concise scan summary (counts, duplicates, errors)

Review Change Stack

…w invites from Outlook + Teams

Closes santifer#664

- scan-outlook.mjs: zero-LLM interview invite scanner via Microsoft Graph REST API
- Covers personal outlook.com and enterprise Azure AD tenants (Fortune 500)
- Zero extra npm dependencies — Node.js 18+ native fetch only
- OAuth2 flow on port 3001 (no conflict with Google Calendar on 3000)
- Extracts: company, role, date/time, Teams/Zoom meeting link from email
- Auto-updates applications.md status to Interview on detection
- Auto-creates interview prep file skeleton in interview-prep/
- Dedup by company+role — safe to run multiple times
- --dry-run, --days N, --auth flags consistent with scan.mjs conventions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2b6c54c3-4c28-41ff-994f-42af1abce701

📥 Commits

Reviewing files that changed from the base of the PR and between dcb787c and 7c3407f.

📒 Files selected for processing (1)
  • scan-outlook.mjs

📝 Walkthrough

Walkthrough

New CLI script scan-outlook.mjs integrates Microsoft Graph API to automatically detect interview confirmation emails from Outlook, extract candidate, role, and meeting details, deduplicate against existing tracker, update application status, generate interview prep files, and log scan history—supporting OAuth2 authentication, configurable scan window, and dry-run preview mode.

Changes

Outlook Interview Detection

Layer / File(s) Summary
OAuth2 token and configuration
scan-outlook.mjs
Reads Microsoft OAuth client ID and tenant from config/profile.yml, implements OAuth2 authorization code flow with local HTTP callback on port 3001, exchanges authorization code for access and refresh tokens, and persists tokens to calendar/ms-token.json for token refresh on subsequent runs. Supports --auth flag to trigger initial login.
Email scanning and interview metadata extraction
scan-outlook.mjs
Calls Microsoft Graph /me/messages endpoint filtered by received date (configurable via --days N, default 7) and subject keywords (Interview, Schedule, Confirm, etc.). Extracts structured fields from each matching message: sender domain as company, subject line to detect role title, body preview for datetime and meeting link (Teams, Zoom). Derives uniqueness key from company + role pair and deduplicates against in-memory set loaded from applications.md to skip already-tracked candidates.
Application tracker and prep file updates
scan-outlook.mjs
Updates data/applications.md to mark matched rows with "Interview" status. Creates interview prep markdown file under interview-prep/{company}-{role}.md without overwriting existing files. Appends scan result record (timestamp, company, role, sender email, parsed metadata) to data/scan-history.tsv. Prints summary of interview count, duplicate skips, and total scanned messages. All file writes respect --dry-run flag for preview-only runs.
sequenceDiagram
  participant User as CLI User
  participant Script as scan-outlook.mjs
  participant AAD as Azure AD
  participant Graph as Microsoft Graph API
  participant Files as File System (tracker, prep, history)
  
  User->>Script: node scan-outlook.mjs --auth
  Script->>AAD: Start local OAuth callback on :3001
  AAD-->>User: Redirect to Azure AD login
  User->>AAD: Authenticate with credentials
  AAD-->>Script: Authorization code to :3001
  Script->>AAD: Exchange code for access token
  AAD-->>Script: access_token + refresh_token
  Script->>Files: Persist token to calendar/ms-token.json
  
  User->>Script: node scan-outlook.mjs --days 7
  Script->>Script: Load token from cache
  Script->>Graph: GET /me/messages?filter=receivedDateTime gt now-7d AND subject contains interview keywords
  Graph-->>Script: Message list (sender, subject, body preview)
  Script->>Script: Extract metadata (company, role, datetime, link)
  Script->>Script: Dedup against applications.md
  Script->>Files: Update data/applications.md status
  Script->>Files: Create interview-prep/{company}-{role}.md
  Script->>Files: Append to data/scan-history.tsv
  Script-->>User: Print summary (found, duplicates, scanned)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • #660: Both implement mail-scanning CLIs that detect interviews and update the tracker/prep files; this PR addresses the Outlook/Microsoft Graph side complementary to #660 (Gmail).
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding Microsoft Graph SDK integration to auto-detect interview invitations from Outlook and Teams, which directly aligns with the PR's primary objective of implementing a scan-outlook.mjs script.
Linked Issues check ✅ Passed The code implements all core requirements from issue #664: OAuth2 flow with port 3001 callback, token persistence to calendar/ms-token.json, /me/messages querying for interview detection, company/role/date/time/link extraction, and integration with existing tracker/prep file workflows.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the Microsoft Graph scanner as specified in issue #664; no extraneous modifications to unrelated files or functionality are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scan-outlook.mjs`:
- Around line 49-50: The DAYS parsing using daysArg/process.argv and parseInt
can produce NaN for invalid input; update the logic around daysArg and DAYS so
that after parsing parseInt(process.argv[daysArg + 1]) you validate the result
(Number.isFinite or Number.isNaN check and ensure an integer > 0) and if invalid
fall back to the default 7 (and optionally emit a console.warn explaining the
bad value). Make the change where DAYS is defined so DAYS is always a valid
positive integer before any date arithmetic.
- Line 118: The execSync call using interpolated authUrl creates a
command-injection risk; replace the execSync(`start "" "${authUrl}"`) pattern
(symbol: execSync and variable authUrl) with a safe launcher that avoids shell
interpolation—use child_process.spawn (or cross-platform open method) with
shell:false and pass the URL as an argument (or use platform-specific APIs like
open/xdg-open/Start-Process equivalents) so the URL is not interpreted by a
shell; ensure you handle Windows "start" separately by invoking the executable
with args instead of constructing a shell string and catch/log errors.
- Around line 412-416: The code writes to SCAN_HISTORY_PATH without ensuring its
parent directory exists; before calling writeFileSync/appendFileSync in the
block guarded by DRY_RUN and historyRows.length, create the directory for
SCAN_HISTORY_PATH (use path.dirname(SCAN_HISTORY_PATH)) and call
fs.mkdirSync(..., { recursive: true }) if it does not exist so writeFileSync and
appendFileSync won't throw ENOENT; update the block around
existsSync/appendFileSync to first ensure the directory exists, then proceed
with writing the header and appending historyRows.
- Around line 280-291: The YAML frontmatter template in the content string uses
unquoted values for company and role which can break when those variables
contain YAML-special characters; update the template in scan-outlook.mjs (the
content constant construction) to properly escape or wrap ${company} and ${role}
in safe YAML strings (for example, always quote them or run them through an
escaping function) so the title, company and role fields produce valid YAML
regardless of characters; modify the code that builds content (the content
constant) to apply the escaping/quoting function to company and role before
interpolation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 651e1526-08d3-4f6a-ae8f-9b494f9c963f

📥 Commits

Reviewing files that changed from the base of the PR and between 5d1f3a3 and dcb787c.

📒 Files selected for processing (1)
  • scan-outlook.mjs

Comment thread scan-outlook.mjs Outdated
Comment thread scan-outlook.mjs Outdated
Comment thread scan-outlook.mjs
Comment thread scan-outlook.mjs
- Validate --days argument: NaN/invalid values fall back to default 7
- Replace execSync shell interpolation with spawn shell:false (command injection fix)
- Create parent directory before writing scan-history.tsv (ENOENT fix)
- Quote company/role values in YAML frontmatter (special character safety)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Microsoft Graph SDK integration — auto-detect interview invites from Outlook + Teams and sync to calendar + prep files

1 participant