Interview Email Parser

On-device regex + NSDataDetector engine that extracts date, time, time zone, meeting URL, and company name from interview invitation emails. Presents a confirmation sheet to match the parsed interview to a tracked job and add an EKEvent to the user's calendar.

Preview

Email / Share text
Parsed fields
high confidence
CompanyMeridian
Date & TimeThursday, June 12th at 2:00 PM
Time ZoneEST
PlatformZoom
Meeting URLzoom.us/j/…

Edit the email text and click Parse to see field extraction in real time

Signal extraction

The parser uses two different strategies for date/time vs. other fields.

FieldStrategyFallback
Date & TimeNSDataDetector (.date) — handles most natural-language formatsRegex fallback for 'Thursday June 12th at 2 PM'
Time ZoneRegex abbreviation map (EST→America/New_York, AEST→Australia/Sydney, etc.)Device local time zone
CompanyRegex: 'at/with/from [the] [Org] team'Linked Job.company field
Meeting URLWhitespace-tokenise → URL(string:) → match zoom/meet/teams prefixmeetingPlatform = 'phone'
RoleLinked Job.title (not extracted from email)—

Design notes

→
Always show a confirmation step — Never auto-create a calendar event without user review. The parsed result has variable confidence — show all extracted fields, allow edits, and require an explicit tap to commit.
→
Confidence indicator prevents false trust — Classify confidence as High (date + time + meeting URL all found), Medium (date or time found, but not both), or Low (no date detected). Surface this visually so the user knows to inspect Low results carefully.
→
Job matcher by fuzzy name — Filter tracked jobs by company name similarity (lowercased contains-match is sufficient). Show top 3 suggestions. Always include a 'None / New Job' option to avoid forcing a bad match.
→
Share Extension is the primary entry point — The user shares plain text from Mail.app or any email client. The extension parses the text and writes a PendingInterviewEntry to the App Group container. The main app reads and clears this entry on next launch.
→
Paste fallback for in-app use — Also expose a 'Parse Email' toolbar button inside JobDetailView. This opens a plain text-paste sheet, feeds it to the same InterviewParser, and presents InterviewConfirmationSheet directly — no Share Extension needed.

Implementation

InterviewParser.ts
tsx
// Web reference — Interview email parser (TypeScript)
export interface ParsedInterview {
  company?:         string;
  dateTime?:        Date;
  timeZoneName?:    string;
  meetingURL?:      string;
  meetingPlatform?: "zoom" | "meet" | "teams" | "phone";
  rawText:          string;
}

const PLATFORM_PATTERNS: [RegExp, "zoom" | "meet" | "teams"][] = [
  [/zoom\.us\/j\/[\d]+/i, "zoom"],
  [/meet\.google\.com\/[a-z-]+/i, "meet"],
  [/teams\.microsoft\.com\/l\/meetup/i, "teams"],
];

const TZ_MAP: Record<string, string> = {
  EST: "America/New_York", PST: "America/Los_Angeles",
  CST: "America/Chicago",  MST: "America/Denver",
  GMT: "Europe/London",    UTC: "UTC",
  AEST: "Australia/Sydney", SGT: "Asia/Singapore",
};

export function parseInterviewEmail(text: string): ParsedInterview {
  const result: ParsedInterview = { rawText: text };

  // Company
  const compMatch = text.match(/(?:at|with) the ([A-Z][A-Za-z ]+?) team/);
  if (compMatch) result.company = compMatch[1];

  // Meeting URL
  for (const [pattern, platform] of PLATFORM_PATTERNS) {
    const match = text.match(pattern);
    if (match) {
      result.meetingURL      = `https://${match[0]}`;
      result.meetingPlatform = platform;
      break;
    }
  }
  if (!result.meetingPlatform) result.meetingPlatform = "phone";

  // Time zone
  const tzMatch = text.match(/\b(EST|PST|CST|MST|GMT|UTC|AEST|SGT)\b/);
  if (tzMatch) result.timeZoneName = TZ_MAP[tzMatch[1]] ?? tzMatch[1];

  // Date — naive extraction, production should use date-fns or chrono-node
  const dateMatch = text.match(
    /(?:monday|tuesday|wednesday|thursday|friday|saturday|sunday)[,\s]+\w+ \d{1,2}(?:th|st|nd|rd)?/i
  );
  const timeMatch = text.match(/\d{1,2}(?::\d{2})?\s*(?:AM|PM)/i);
  if (dateMatch && timeMatch) {
    result.dateTime = new Date(`${dateMatch[0]} ${timeMatch[0]}`);
  }

  return result;
}

Privacy & entitlements

KeyPlatformRequired for
NSCalendarsWriteOnlyAccessUsageDescriptioniOS 17+EKEventStore.requestWriteOnlyAccessToEvents
NSCalendarsUsageDescriptionmacOSEKEventStore.requestAccess(to: .event)
com.apple.security.personal-information.calendarsmacOS entitlementHardened runtime calendar access

Accessibility

  • →The confirmation sheet must be presented as a modal with accessibilityViewIsModal(true) so VoiceOver does not read underlying content.
  • →Each parsed field row should have an accessibilityLabel combining the field name and value: 'Date and time: Thursday June 12 at 2 PM EST'.
  • →The confidence indicator must not rely on colour alone — include the word (High / Medium / Low) in the accessible label.
  • →The 'Add to Calendar' button should briefly announce success via UIAccessibility.post(notification: .announcement, argument: 'Interview added to calendar') after the event is saved.