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 stepNever 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 trustClassify 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 nameFilter 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 pointThe 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 useAlso 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.