Skip to content
Tier 2 Schema

Parse Job Application Emails

Turn Greenhouse, Lever, Workday, and Ashby candidate-status emails into typed JSON — application status, role, company, recruiter, posting ID, dates, and offer details. Schema-first.

Every applicant-tracking system — Greenhouse, Lever, Workday, Ashby, SmartRecruiters, iCIMS — sends the candidate a stream of status emails as an application moves through the pipeline: “Application received”, “Phone screen scheduled”, “Onsite interview”, “Offer”, “Rejection”. Each ATS lays the message out differently, but a job-tracker, a recruiting-analytics dashboard, or a candidate-experience tool always needs the same handful of fields: what stage the application is at, which role and company, at which ATS, who the recruiter is, and when the next thing happens. MailFrame extracts those fields from the notification email into typed, schema-validated JSON you can route straight into an application tracker, a stage-change webhook, or an offer-alerting workflow.

It works on the body of application-status emails from every major ATS — Greenhouse (no-reply@greenhouse.io and tenant *.greenhouse-mail.io addresses), Lever (no-reply@hire.lever.co), Workday (tenant *.myworkday.com addresses), Ashby (notifications@ashbyhq.com), SmartRecruiters, and iCIMS. Match on the message body and the ATS-specific wording (“we received your application”, “your interview is scheduled”, “an offer is on its way”) rather than the From address alone — companies relay these through tenant-specific and white-labeled careers domains. The application stage, role, company, ATS provider, job posting ID, recruiter, event date, and next step all normalize into one consistent shape regardless of which system sent the message. PDF, image, and calendar-attachment input are on the roadmap; inbox forwarding is planned.

Fields MailFrame extracts

FieldTypeExampleNotes
notification_typeenumapplication_receivedOne of application_received, screening_scheduled, interview_scheduled, interview_completed, offer_extended, offer_accepted, offer_declined, rejected, withdrawn
application_idstring88472013The ATS’s internal application/candidate ID, when present — the dedup key across a candidate’s lifecycle
job_titlestringSenior Backend EngineerThe role as written on the posting
company_namestringAcme CoThe hiring company — the employer, not the ATS vendor
company_domainstringacme.exampleSending or careers domain
ats_providerenumgreenhouseOne of greenhouse, lever, workday, ashby, smartrecruiters, icims, unknown
job_posting_idstring4025001The vendor’s external job ID — Greenhouse’s numeric job ID, Lever’s UUID, Workday’s jobRequisitionId
job_locationstringRemote (US)Posting location — e.g. Remote, New York, NY
recruiter_namestringPriya ShahRecruiter or coordinator named on the email
recruiter_emailstringpriya.shah@acme.exampleRecruiter’s reply-to address when present
hiring_manager_namestringDev PatelHiring manager named on the email, when present
event_datestring2026-07-10T15:00:00-04:00Date/time of the interview, offer, or rejection, ISO 8601
event_locationstringAcme HQ, 500 Market St, San Francisco, CAPhysical address or Remote for a scheduled interview
event_location_urlstringhttps://meet.google.com/abc-defg-hijConferencing link for a remote interview when present
next_stepstringOur recruiting team will review your application and be in touch.Free-text next-action description from the email
status_urlstringhttps://boards.greenhouse.io/acme/jobs/4025001/statusLink to the candidate’s application status page
datestring2026-07-03Date the email was sent, normalized to ISO 8601

Sample input

A typical Greenhouse “application received” email looks like this:

From: Acme Co Recruiting <no-reply@greenhouse.io>
Subject: We received your application for Senior Backend Engineer
Date: Thu, 03 Jul 2026 09:12:00 -0400
To: jordan.rivera@example.com

Hi Jordan,

Thanks for applying to the Senior Backend Engineer role at Acme Co. Our
recruiting team has received your application and will review it shortly.

Position:     Senior Backend Engineer
Location:     Remote (US)
Requisition:  4025001
Recruiter:    Priya Shah <priya.shah@acme.example>

We'll be in touch about next steps. In the meantime, you can check the
status of your application any time.

View application:
https://boards.greenhouse.io/acme/jobs/4025001/status

The same shape is produced for Lever, Workday, Ashby, SmartRecruiters, and iCIMS status emails — notification_type distinguishes an application_received acknowledgement from a screening_scheduled or interview_scheduled invite (which carry event_date, event_location, and event_location_url), an interview_completed recap, an offer_extended notice, an offer_accepted / offer_declined confirmation, a rejected decision, or a withdrawn update — and the rest of the fields normalize to the same JSON keys regardless of which ATS sent the message.

Structured JSON output

{
  "notification_type": "application_received",
  "application_id": "88472013",
  "job_title": "Senior Backend Engineer",
  "company_name": "Acme Co",
  "company_domain": "acme.example",
  "ats_provider": "greenhouse",
  "job_posting_id": "4025001",
  "job_location": "Remote (US)",
  "recruiter_name": "Priya Shah",
  "recruiter_email": "priya.shah@acme.example",
  "next_step": "Our recruiting team will review your application and be in touch about next steps.",
  "status_url": "https://boards.greenhouse.io/acme/jobs/4025001/status",
  "date": "2026-07-03"
}

JSON Schema definition

Every field is validated against the schema before MailFrame returns it. You can copy this as a starting point and tighten it for your own use case — for example, require event_date if every notification in your workflow is an interview invite, or restrict notification_type to {"offer_extended", "rejected"} if you only route terminal decisions:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "job_application",
  "type": "object",
  "required": [
    "notification_type",
    "job_title",
    "company_name",
    "ats_provider"
  ],
  "properties": {
    "notification_type": {
      "type": "string",
      "enum": [
        "application_received",
        "screening_scheduled",
        "interview_scheduled",
        "interview_completed",
        "offer_extended",
        "offer_accepted",
        "offer_declined",
        "rejected",
        "withdrawn"
      ]
    },
    "application_id":      { "type": "string" },
    "job_title":          { "type": "string", "minLength": 1 },
    "company_name":       { "type": "string", "minLength": 1 },
    "company_domain":     { "type": "string", "format": "hostname" },
    "ats_provider": {
      "type": "string",
      "enum": ["greenhouse", "lever", "workday", "ashby", "smartrecruiters", "icims", "unknown"]
    },
    "job_posting_id":     { "type": "string" },
    "job_location":       { "type": "string" },
    "recruiter_name":     { "type": "string" },
    "recruiter_email":    { "type": "string", "format": "email" },
    "hiring_manager_name":{ "type": "string" },
    "event_date":         { "type": "string", "format": "date-time" },
    "event_location":     { "type": "string" },
    "event_location_url": { "type": "string", "format": "uri" },
    "next_step":          { "type": "string" },
    "status_url":         { "type": "string", "format": "uri" },
    "date":               { "type": "string", "format": "date" }
  }
}

Parse via the API

POST the raw email (MIME or plain text) to /v1/parse with the schema you want to extract against:

curl https://api.mailframe.ai/v1/parse \
  -H "Authorization: Bearer ${MAILFRAME_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "schema_id": "job_application",
    "raw_mime": "From: Acme Co Recruiting <no-reply@greenhouse.io>\r\nTo: jordan.rivera@example.com\r\nSubject: We received your application for Senior Backend Engineer\r\nDate: Thu, 03 Jul 2026 09:12:00 -0400\r\n\r\nHi Jordan,\r\n\r\nThanks for applying to the Senior Backend Engineer role at Acme Co. Our recruiting team has received your application and will review it shortly.\r\n\r\nPosition:     Senior Backend Engineer\r\nLocation:     Remote (US)\r\nRequisition:  4025001\r\nRecruiter:    Priya Shah <priya.shah@acme.example>\r\n\r\nWe will be in touch about next steps. In the meantime, you can check the status of your application any time.\r\n\r\nView application:\r\nhttps://boards.greenhouse.io/acme/jobs/4025001/status"
  }'

The call is synchronous: /v1/parse validates the extraction against your schema and returns the typed JSON in the HTTP response, so you can act on it inline — advance the application’s stage in your tracker, fire a Slack ping on offer_extended, or open a candidate-experience follow-up on rejected.

Routing on the result

The most useful split is by notification_type: it is the lane-decider. An offer_extended deserves a different lane than a rejected — the first is a high-signal event that should page the candidate (or, on the employer side, the offer-approval workflow), while the second is an archival stage change. interview_scheduled and screening_scheduled carry the event_date, event_location, and event_location_url you need to drop the interview onto a calendar. Use application_id (when present) as the deduplication key — an ATS sends one message per stage transition, so a single application produces a series of emails over its lifetime, and the same application_id is the right join across all of them. When application_id is absent, fall back to (company_name, job_posting_id, job_title).

Signed webhook delivery

Prefer asynchronous delivery? Signed webhook delivery — where MailFrame POSTs the extraction result to your endpoint with an HMAC-SHA256 signature in the MailFrame-Signature header and exponential-backoff retries — is available during early access alongside the synchronous API. Inbox forwarding — pointing a Gmail or Outlook filter at a unique inbox address MailFrame assigns you — is on the roadmap.

Working with related email types? See the Parse Linear Notifications schema, the Parse GitHub Notifications schema, and the email-to-JSON API guide for a full walkthrough of POST /v1/parse. For a step-by-step tutorial that wires this schema into a real /v1/parse request, see Parse Job Application Emails to JSON.

Other schemas

Ship this schema in production

Define your fields once, then POST raw email to /v1/parse. MailFrame returns typed JSON in the HTTP response or via signed webhook delivery with retries, attempt history, dead-letter, and replay. (Inbox forwarding is on the roadmap.)

Request early access