From 2eb0c07185645f6074e021dee9797974d4ba687c Mon Sep 17 00:00:00 2001 From: Rachel Petry <287457313+rachel-petry@users.noreply.github.com> Date: Tue, 3 Nov 2026 16:04:21 -0600 Subject: [PATCH] feat(classifier): add recruiter outreach fast-path and Eventbrite ticketing and promos --- src/gmail-ai-classifier/code.gs | 11 ++-- src/gmail-ai-classifier/src/index.js | 11 ++-- src/gmail-ai-classifier/tests/code.test.js | 63 ++++++++++++++++++++++ 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/gmail-ai-classifier/code.gs b/src/gmail-ai-classifier/code.gs index 2793029a..bf9d8c6f 100644 --- a/src/gmail-ai-classifier/code.gs +++ b/src/gmail-ai-classifier/code.gs @@ -1293,10 +1293,10 @@ const MANUAL_PAYMENT_SNIPPET_TERMS_ = 'autopay is disabled|autopay is off|autopay is not enabled|auto-pay is off|not enrolled in autopay|please pay|pay by|pay now|payment is due|amount due|balance due|payment due|payment request|money request|sent you an invoice|sent you a money request|to avoid interruption|to avoid a late fee|to avoid late fees|manual payment' const CAREER_SUBJECT_TERMS_ = - 'employment agreement|employment contract|offer letter|job offer|employment offer|terms of employment|independent contractor agreement|restrictive covenant|confidentiality agreement|interview confirmation|interview invitation|invitation to interview|interview with |schedule an interview|recruiter screen|phone screen|interview schedule|schedule your interview|interview availability|next round of interview|next round of interviews|round 2 interview|interview debrief|next steps in the interview|update on your candidacy|update on your application|your application for|application status:' + 'employment agreement|employment contract|offer letter|job offer|employment offer|terms of employment|independent contractor agreement|restrictive covenant|confidentiality agreement|interview confirmation|interview invitation|invitation to interview|interview with |schedule an interview|recruiter screen|phone screen|interview schedule|schedule your interview|interview availability|next round of interview|next round of interviews|round 1 interview|round 2 interview|round 3 interview|final round interview|panel interview|leadership interview|interview follow up|interview follow-up|interview debrief|next steps in the interview|connecting on |opportunities at |introductory phone call|introductory call|time to connect regarding|update on your candidacy|update on your application|your application for|application status:' const CAREER_SNIPPET_TERMS_ = - 'employment agreement|offer of employment|terms of employment|pleased to offer you the position|pleased to offer you|pleased to confirm our offer|congratulations on your offer|employment document|restrictive covenants agreement|interview schedule|schedule your interview|schedule an interview|interview availability|next round of interview|next round of interviews|next steps in the interview|interview with our team|speak with our team|discuss the role|candidacy for the position|your application for the position|invitation to interview' + 'employment agreement|offer of employment|terms of employment|pleased to offer you the position|pleased to offer you|pleased to confirm our offer|congratulations on your offer|employment document|restrictive covenants agreement|interview schedule|schedule your interview|schedule an interview|interview availability|next round of interview|next round of interviews|next steps in the interview|interview with our team|speak with our team|discuss the role|candidacy for the position|your application for the position|invitation to interview|talent acquisition|reaching out regarding your application|reaching out regarding your resume|regarding your resume and application|introductory phone call|introductory call|schedule a next step interview|scheduling a next step interview|discuss opportunities at|discuss the opportunity' const DOCUMENT_SIGNATURE_COMPLETED_TERMS_ = 'completed: docusign|docusign: completed|has completed signing|all parties have signed|document is completed|signed copy' @@ -1504,7 +1504,8 @@ const DETERMINISTIC_RULE_SPECS_ = [ 'sender+sub::homedepot.com::::::order|receipt|picked up|ready for pickup::::02_Finance_Legal::Finance/Purchases::Updates::archive::0.98::Retail Order / Receipt::Home Depot store receipt or order pickup confirmation.', 'sender+sub::target.com|costco.com|bestbuy.com|walmart.com::::::order|receipt|ready for pickup|picked up|shipped|delivered::::02_Finance_Legal::Finance/Purchases::Updates::archive::0.98::Retail Order / Receipt::Retail store order confirmation, receipt, or pickup notice.', 'sender+sub::ereceiptshell.com|autozone.com|advanceautoparts.com|rockauto.com|discounttire.com|tirerack.com::::shell e-receipt::receipt|purchase|order|fuel|tire|tires|appointment::::03_Vehicles::Vehicles/Maintenance::Updates::archive::0.99::Vehicle Fuel/Maintenance Receipt::Automated vehicle fuel purchase receipt or parts order confirmation routed to Vehicles/Maintenance.', - 'sender+sub::evite.com|paperlesspost.com|punchbowl.com|partiful.com::::::sale|deal|discount|% off|promo|templates|browse|ideas|invitation designs|holiday collection|cards collection::::::::Promotions::archive::0.98::Event Platform Promo::Event platform promotional marketing or card template sale routed to Promotions.', + 'sender+sub::evite.com|paperlesspost.com|punchbowl.com|partiful.com|eventbrite.com::::::sale|deal|discount|% off|promo|templates|browse|ideas|invitation designs|holiday collection|cards collection|trending events|popular events|events near you|events in |recommended for you|events you may like|top picks for you|discover events|things to do::::::::Promotions::archive::0.98::Event Platform Promo::Event platform promotional marketing or card template sale routed to Promotions.', + "sender+sub::eventbrite.com::::::your tickets for|order confirmation for|you're going to|you are going to|registration confirmation|ticket confirmation|tickets for your upcoming::::01_Household::Household/General::Updates::archive::0.99::Event Registration / Tickets::Event ticket or registration confirmation notice routed to Updates.", 'any::nytimes.com|nextdoor.com|substack.com|redditmail.com|daily.therundown.ai|therundown.ai|mail.instagram.com|instagram.com|facebookmail.com|linkedin.com|pinterest.com|quora.com|youtube.com|twitter.com|x.com::::the passion translation|aibreakfast::::sent you a message|sent you an inmail|new message|messaged you|replied to your|direct message|inmail|security|verify|verification|password|login|sign-in|action required|urgent::::::Social::archive::0.98::News/Newsletter/Social::Routine media newsletter, Substack digest, social media alert, or political appeal.', ] @@ -1825,10 +1826,10 @@ function deterministicRuleClassify_(sender, subject, snippet, thread, config) { ) } - // 01_Household: Event Invitation & RSVP Shield (Evite, Paperless Post, Punchbowl, Partiful) + // 01_Household: Event Invitation & RSVP Shield (Evite, Paperless Post, Punchbowl, Partiful, Eventbrite) const isEventPlatformSender = matchesSenderDomain_( sender, - 'evite.com|paperlesspost.com|punchbowl.com|partiful.com' + 'evite.com|paperlesspost.com|punchbowl.com|partiful.com|eventbrite.com' ) const isInvitationOrRsvp = containsAny_(subLower, EVENT_INVITATION_TERMS_) || diff --git a/src/gmail-ai-classifier/src/index.js b/src/gmail-ai-classifier/src/index.js index 7672eaa6..b01dc26a 100644 --- a/src/gmail-ai-classifier/src/index.js +++ b/src/gmail-ai-classifier/src/index.js @@ -1288,10 +1288,10 @@ const MANUAL_PAYMENT_SNIPPET_TERMS_ = 'autopay is disabled|autopay is off|autopay is not enabled|auto-pay is off|not enrolled in autopay|please pay|pay by|pay now|payment is due|amount due|balance due|payment due|payment request|money request|sent you an invoice|sent you a money request|to avoid interruption|to avoid a late fee|to avoid late fees|manual payment' const CAREER_SUBJECT_TERMS_ = - 'employment agreement|employment contract|offer letter|job offer|employment offer|terms of employment|independent contractor agreement|restrictive covenant|confidentiality agreement|interview confirmation|interview invitation|invitation to interview|interview with |schedule an interview|recruiter screen|phone screen|interview schedule|schedule your interview|interview availability|next round of interview|next round of interviews|round 2 interview|interview debrief|next steps in the interview|update on your candidacy|update on your application|your application for|application status:' + 'employment agreement|employment contract|offer letter|job offer|employment offer|terms of employment|independent contractor agreement|restrictive covenant|confidentiality agreement|interview confirmation|interview invitation|invitation to interview|interview with |schedule an interview|recruiter screen|phone screen|interview schedule|schedule your interview|interview availability|next round of interview|next round of interviews|round 1 interview|round 2 interview|round 3 interview|final round interview|panel interview|leadership interview|interview follow up|interview follow-up|interview debrief|next steps in the interview|connecting on |opportunities at |introductory phone call|introductory call|time to connect regarding|update on your candidacy|update on your application|your application for|application status:' const CAREER_SNIPPET_TERMS_ = - 'employment agreement|offer of employment|terms of employment|pleased to offer you the position|pleased to offer you|pleased to confirm our offer|congratulations on your offer|employment document|restrictive covenants agreement|interview schedule|schedule your interview|schedule an interview|interview availability|next round of interview|next round of interviews|next steps in the interview|interview with our team|speak with our team|discuss the role|candidacy for the position|your application for the position|invitation to interview' + 'employment agreement|offer of employment|terms of employment|pleased to offer you the position|pleased to offer you|pleased to confirm our offer|congratulations on your offer|employment document|restrictive covenants agreement|interview schedule|schedule your interview|schedule an interview|interview availability|next round of interview|next round of interviews|next steps in the interview|interview with our team|speak with our team|discuss the role|candidacy for the position|your application for the position|invitation to interview|talent acquisition|reaching out regarding your application|reaching out regarding your resume|regarding your resume and application|introductory phone call|introductory call|schedule a next step interview|scheduling a next step interview|discuss opportunities at|discuss the opportunity' const DOCUMENT_SIGNATURE_COMPLETED_TERMS_ = 'completed: docusign|docusign: completed|has completed signing|all parties have signed|document is completed|signed copy' @@ -1499,7 +1499,8 @@ const DETERMINISTIC_RULE_SPECS_ = [ 'sender+sub::homedepot.com::::::order|receipt|picked up|ready for pickup::::02_Finance_Legal::Finance/Purchases::Updates::archive::0.98::Retail Order / Receipt::Home Depot store receipt or order pickup confirmation.', 'sender+sub::target.com|costco.com|bestbuy.com|walmart.com::::::order|receipt|ready for pickup|picked up|shipped|delivered::::02_Finance_Legal::Finance/Purchases::Updates::archive::0.98::Retail Order / Receipt::Retail store order confirmation, receipt, or pickup notice.', 'sender+sub::ereceiptshell.com|autozone.com|advanceautoparts.com|rockauto.com|discounttire.com|tirerack.com::::shell e-receipt::receipt|purchase|order|fuel|tire|tires|appointment::::03_Vehicles::Vehicles/Maintenance::Updates::archive::0.99::Vehicle Fuel/Maintenance Receipt::Automated vehicle fuel purchase receipt or parts order confirmation routed to Vehicles/Maintenance.', - 'sender+sub::evite.com|paperlesspost.com|punchbowl.com|partiful.com::::::sale|deal|discount|% off|promo|templates|browse|ideas|invitation designs|holiday collection|cards collection::::::::Promotions::archive::0.98::Event Platform Promo::Event platform promotional marketing or card template sale routed to Promotions.', + 'sender+sub::evite.com|paperlesspost.com|punchbowl.com|partiful.com|eventbrite.com::::::sale|deal|discount|% off|promo|templates|browse|ideas|invitation designs|holiday collection|cards collection|trending events|popular events|events near you|events in |recommended for you|events you may like|top picks for you|discover events|things to do::::::::Promotions::archive::0.98::Event Platform Promo::Event platform promotional marketing or card template sale routed to Promotions.', + "sender+sub::eventbrite.com::::::your tickets for|order confirmation for|you're going to|you are going to|registration confirmation|ticket confirmation|tickets for your upcoming::::01_Household::Household/General::Updates::archive::0.99::Event Registration / Tickets::Event ticket or registration confirmation notice routed to Updates.", 'any::nytimes.com|nextdoor.com|substack.com|redditmail.com|daily.therundown.ai|therundown.ai|mail.instagram.com|instagram.com|facebookmail.com|linkedin.com|pinterest.com|quora.com|youtube.com|twitter.com|x.com::::the passion translation|aibreakfast::::sent you a message|sent you an inmail|new message|messaged you|replied to your|direct message|inmail|security|verify|verification|password|login|sign-in|action required|urgent::::::Social::archive::0.98::News/Newsletter/Social::Routine media newsletter, Substack digest, social media alert, or political appeal.', ] @@ -1820,10 +1821,10 @@ function deterministicRuleClassify_(sender, subject, snippet, thread, config) { ) } - // 01_Household: Event Invitation & RSVP Shield (Evite, Paperless Post, Punchbowl, Partiful) + // 01_Household: Event Invitation & RSVP Shield (Evite, Paperless Post, Punchbowl, Partiful, Eventbrite) const isEventPlatformSender = matchesSenderDomain_( sender, - 'evite.com|paperlesspost.com|punchbowl.com|partiful.com' + 'evite.com|paperlesspost.com|punchbowl.com|partiful.com|eventbrite.com' ) const isInvitationOrRsvp = containsAny_(subLower, EVENT_INVITATION_TERMS_) || diff --git a/src/gmail-ai-classifier/tests/code.test.js b/src/gmail-ai-classifier/tests/code.test.js index 901da956..30b6b96b 100644 --- a/src/gmail-ai-classifier/tests/code.test.js +++ b/src/gmail-ai-classifier/tests/code.test.js @@ -4420,6 +4420,30 @@ describe('reclassifyThreadsByQuery Historical Realignment', () => { ) expect(alertRes).toBeNull() }) + + test('classifies direct recruiter outreach and interview scheduling into 06_Work_Career', () => { + const recruiterRes = mod.deterministicRuleClassify_( + 'recruiter@firm.example', + 'Connecting on Leadership Opportunities', + 'Our talent acquisition team reached out regarding your resume and application. Are you open for an introductory phone call this week?' + ) + expect(recruiterRes).not.toBeNull() + expect(recruiterRes.canonicalDomain).toBe('06_Work_Career') + expect(recruiterRes.subLabel).toBe('Work/Career') + expect(recruiterRes.category).toBe('Primary') + expect(recruiterRes.action).toBe('keep') + + const followUpRes = mod.deterministicRuleClassify_( + 'interviewer@techcorp.example', + 'Interview Follow-up: Senior Technical Lead', + 'Thank you for speaking with our leadership team. We would like to schedule a next step interview.' + ) + expect(followUpRes).not.toBeNull() + expect(followUpRes.canonicalDomain).toBe('06_Work_Career') + expect(followUpRes.subLabel).toBe('Work/Career') + expect(followUpRes.category).toBe('Primary') + expect(followUpRes.action).toBe('keep') + }) }) }) }) @@ -4554,6 +4578,45 @@ describe('reclassifyThreadsByQuery Historical Realignment', () => { expect(promoRes.category).toBe('Promotions') expect(promoRes.action).toBe('archive') expect(promoRes.confidence).toBe(0.98) + + const ebPromoRes = mod.deterministicRuleClassify_( + 'updates@eventbrite.com', + 'Trending events near you this weekend', + 'Discover events and popular things to do in your area.' + ) + expect(ebPromoRes).not.toBeNull() + expect(ebPromoRes.canonicalDomain).toBeNull() + expect(ebPromoRes.category).toBe('Promotions') + expect(ebPromoRes.action).toBe('archive') + expect(ebPromoRes.confidence).toBe(0.98) + }) + + test('classifies Eventbrite event invitation into 01_Household Primary keep', () => { + const res = mod.deterministicRuleClassify_( + 'invitations@eventbrite.com', + "You're invited to Annual Community Dinner", + 'You are invited to join us for dinner. RSVP today.' + ) + expect(res).not.toBeNull() + expect(res.canonicalDomain).toBe('01_Household') + expect(res.subLabel).toBe('Household/General') + expect(res.category).toBe('Primary') + expect(res.action).toBe('keep') + expect(res.confidence).toBe(1.0) + }) + + test('routes Eventbrite tickets and order confirmation to Household/General Updates archive', () => { + const ticketRes = mod.deterministicRuleClassify_( + 'orders@eventbrite.com', + 'Your tickets for Tech Leadership Summit 2026', + 'Order confirmation for 2 general admission tickets.' + ) + expect(ticketRes).not.toBeNull() + expect(ticketRes.canonicalDomain).toBe('01_Household') + expect(ticketRes.subLabel).toBe('Household/General') + expect(ticketRes.category).toBe('Updates') + expect(ticketRes.action).toBe('archive') + expect(ticketRes.confidence).toBe(0.99) }) test('falls through for non-invitation non-promotional event platform notices', () => {