diff --git a/site/src/pages/use-cases/recruiters.astro b/site/src/pages/use-cases/recruiters.astro index 1b555a016..2387e619b 100644 --- a/site/src/pages/use-cases/recruiters.astro +++ b/site/src/pages/use-cases/recruiters.astro @@ -4,112 +4,177 @@ import HeroAtmosphere from '../../components/HeroAtmosphere.astro'; import Icon from '../../components/Icon.astro'; import CTA from '../../components/CTA.astro'; -// Three-step candidate sequence preview. Plain text, no HTML. -// Uses variables rather than fictional company specifics. -const sequence = [ +// =========================================================================== +// Recruiters use case. Every number is a real Warmbly constant, not a measured +// outcome: cold cap defaults to 50/day and is validated up to 100 max +// (internal/config/constants.go, internal/repository/pg_email.go), 600s minimum +// gap, warmup 10 to 40/day at +1/day in the premium pool. The 30/day recruiter +// figure is a recommendation. +// +// Astro JSX caveat: no `<`, `>`, or `<=` may appear inside a {} expression, and +// no literal `{{ }}` may appear in markup (it parses as an expression), so Go +// template braces are written as {`{{...}}`} template literals where needed. +// =========================================================================== + +// --- Section 1 · two senders, one candidate ------------------------------- +const specimens = [ { - day: 'Day 0', - label: 'First touch', - subject: '{{ first_name }}, quick question about your work', - body: -`Hi {{ first_name }}, - -I came across your work at {{ current_company }} and what you have been shipping recently. The role I am hiring for lines up closely with what you are doing today. - -It is a {{ open_role }} reporting to the {{ reports_to }}. Remote-friendly, compensation in band for the role. - -Would you be open to a 20-minute intro call next week? - -{{ sender.first_name }}`, + label: 'Shared inbox', + ring: 'ring-rose-200', + tag: 'Promotions', + tagClass: 'bg-amber-50 text-amber-700 ring-amber-200', + avatar: 'C', + avatarClass: 'bg-rose-50 text-rose-600 ring-rose-200', + from: 'Careers at Acme', + addr: 'careers@acme.com', + subject: 'Exciting opportunity at Acme, apply today', + time: '9:02 AM', + connector: 'bg-rose-300', + dot: 'bg-rose-400', + callouts: [ + 'Shared by the whole team', + 'Carries everyone’s complaint history', + "No sending history that's clearly one person's", + ], }, { - day: 'Day 5', - label: 'Soft follow-up', - subject: 're: {{ first_name }}, quick question about your work', - body: -`Hi {{ first_name }}, - -Looping back on this. Keeping it short, here is the relevant context: - -- {{ open_role }}, {{ company_stage }}, {{ team_size }} -- Compensation in band for the role -- Remote across {{ work_region }} -- The hiring manager came up through the same problem space you are in now - -If the timing is off, a one-line "not now" works. I will not chase. - -{{ sender.first_name }}`, - }, - { - day: 'Day 12', - label: 'Permission close', - subject: 're: {{ first_name }}, quick question about your work', - body: -`Hi {{ first_name }}, - -Last note from me on this. If now is not the moment, I will close the loop and stop reaching out. - -If it is worth a 20-minute conversation later, reply with a month that works and I will set a reminder. - -Either way, thanks for the read. - -{{ sender.first_name }}`, + label: 'Recruiter mailbox', + ring: 'ring-[color:var(--sky-2)]', + tag: 'Primary', + tagClass: 'bg-[color:var(--sky-1)]/60 text-[#0369a1] ring-[color:var(--sky-2)]', + avatar: 'DR', + avatarClass: 'bg-[color:var(--brand-soft)] text-[#0369a1] ring-[color:var(--sky-2)]', + from: 'Dana Reyes', + addr: 'dana@northwind.so', + subject: 'Quick question about your work', + time: '9:04 AM', + connector: 'bg-[color:var(--sky-2)]', + dot: 'bg-[#0284c7]', + callouts: [ + 'Belongs to one recruiter', + 'Builds its own sending history', + 'Replies come straight back to her', + ], }, ]; -// "Why recruiter outreach is different" pain cards -const pains = [ +// --- Section 2 · how the two setups compare ------------------------------- +const ownershipLedger = [ + { label: 'Reputation', shared: 'The address itself, shared by everyone.', mine: 'The individual recruiter.' }, + { label: 'One complaint', shared: 'Affects everyone who sends from the address.', mine: 'Stays with that one mailbox.' }, + { label: 'What the candidate sees', shared: 'A generic careers address.', mine: 'A named person they can reply to.' }, + { label: 'If a recruiter leaves', shared: 'Their threads stay stuck on the alias.', mine: 'The mailbox goes with them.' }, + { label: 'Warmup', shared: 'Hard to warm an inbox many people send from.', mine: 'Each mailbox warms on its own in the premium pool.' }, +]; + +// --- Section 3 · sending limits ------------------------------------------- +// fillPct / ceilingPct precomputed so no comparison appears in the meter markup. +const parameters = [ { - title: 'Heavy HTML kills reply rate', - body: 'Image-rich job descriptions, logos, sidebar tables and tracking pixels make a candidate email look like a careers-page newsletter. Receivers route it to Promotions.', + id: '01', + label: 'Recommended daily cap', + value: '30 / day', + fillPct: 30, + ceilingPct: 50, + ceiling: '50 default, 100 max', + why: 'We suggest starting here and raising the cap only once a mailbox has a clean record over a few weeks.', }, { - title: 'Shared inbox dilutes signal', - body: 'careers@ or talent@ mailboxes have no reputation of their own. Senders pile on, complaint rate accumulates, every recruiter inherits the same poor sender history.', + id: '02', + label: 'Minimum gap between sends', + value: '600s', + fillPct: 100, + ceilingPct: 100, + ceiling: 'every mailbox', + why: 'A 600 second gap between sends avoids the burst pattern filters associate with bulk tools.', }, { - title: 'Generic templates land in Promotions', - body: 'When the same opening sentence is sent thousands of times across recruiters, providers fingerprint the template and route the family of messages away from the primary inbox.', + id: '03', + label: 'Warmup volume', + value: '10 to 40', + fillPct: 40, + ceilingPct: 100, + ceiling: '+1 / day, premium pool', + why: 'Each mailbox warms on its own and keeps some normal back-and-forth going while it sends.', + }, + { + id: '04', + label: 'Recruiter to mailbox', + value: '1 : 1', + fillPct: 100, + ceilingPct: 100, + ceiling: 'one per person', + why: 'Since reputation sits with the mailbox, each one should belong to a single recruiter.', }, ]; -// Personal mailbox policy. Numbers are explicit recommendations, not measured outcomes. -const mailboxPolicy = [ - { - k: 'One mailbox per recruiter', - v: '1 : 1', - why: 'Reputation is per-mailbox. A recruiter who replies to candidates from their face builds individual sender history that travels with them.', - }, - { - k: 'Recommended daily cap', - v: '30 / day', - why: 'The platform default is 100 / day. We recommend recruiters stay below that. Candidate domains are very fragmented and the cost of one complaint is asymmetric for a recruiter brand.', - }, - { - k: 'Send spacing', - v: '600s gap', - why: 'The default 600s minimum gap between sends is preserved. Bursty candidate sends from one mailbox look automated to provider heuristics.', - }, - { - k: 'Reply detection', - v: 'Auto-suppress', - why: 'Replies that match STOP, REMOVE, NOT INTERESTED, or an OOO bounce auto-suppress the candidate across every sequence in the workspace.', - }, +const paramFootnote = + 'The 30/day figure is a recommendation, not a measured result. The platform sets a 50/day default and allows overrides up to 100. Raise a mailbox toward that only after it has built a clean complaint and bounce record.'; + +// --- Section 4 · how many follow-ups -------------------------------------- +const cadence = [ + { day: 'Day 0', label: 'First message', intent: 'A short, specific question.' }, + { day: 'Day 5', label: 'Follow-up', intent: 'One line of context and an easy out.' }, + { day: 'Day 12', label: 'Last note', intent: 'A final check-in, then it stops.' }, ]; +const phantomTouches = [ + { n: '04', op: 0.5 }, + { n: '05', op: 0.38 }, + { n: '06', op: 0.26 }, + { n: '07', op: 0.16 }, +]; + +// --- Section 5 · plain text ----------------------------------------------- +const stripped = [ + { name: 'Logo banner', icon: 'layers', why: 'reads as a newsletter header' }, + { name: 'Image-based job description', icon: 'spark', why: 'link to a hosted page instead' }, + { name: 'HTML signature with social icons', icon: 'contact', why: 'extra chrome looks promotional' }, + { name: 'Tracking pixel', icon: 'search', why: 'a spam signal and a trust cost' }, +]; + +const cleanLines = [ + 'Hi Maya,', + 'I came across your work and the role lined up with it.', + 'Open to a 20-minute call next week?', +]; + +// --- Section 6 · Go templating -------------------------------------------- +// Merge fields are plain string values, so they are safe to render with {field}. +const mergeFields = ['{{.FirstName}}', '{{.LastName}}', '{{.Company}}', '{{.Role}}', '{{.Email}}']; + +// --- Section 7 · opt-outs -------------------------------------------------- +const suppressTriggers = ['STOP', 'REMOVE', 'NOT INTERESTED', 'OOO bounce']; + +const suppressEndpoints = [ + { label: 'dana@northwind.so', kind: 'mailbox' }, + { label: 'mara@northwind.so', kind: 'mailbox' }, + { label: 'Eng leads · Q2', kind: 'sequence' }, + { label: 'Design senior', kind: 'sequence' }, + { label: 'GTM warm', kind: 'sequence' }, + { label: 'Workspace contact', kind: 'record' }, +]; + +const verdicts = [ + { label: 'Interested', icon: 'spark', chip: 'bg-emerald-50 text-emerald-700 ring-emerald-200', dot: 'bg-emerald-500', does: 'The sequence stops and the reply is flagged for the recruiter.' }, + { label: 'Not now', icon: 'clock', chip: 'bg-amber-50 text-amber-700 ring-amber-200', dot: 'bg-amber-500', does: 'Logged as a decline for now. If the wording is a clear opt-out, the candidate is added to the suppression list.' }, + { label: 'Referral', icon: 'users', chip: 'bg-[color:var(--sky-1)]/50 text-[#0369a1] ring-[color:var(--sky-2)]', dot: 'bg-[#0284c7]', does: 'A pointer to someone else, logged against the contact.' }, +]; + +// --- FAQ ------------------------------------------------------------------ const faq: [string, string][] = [ - ['Why recommend 30/day per recruiter when the platform allows 100?', 'The platform default cap of 100/day exists for high-throughput sales motions where the sender already has a strong sender reputation on a narrow set of recipient domains. Recruiter outreach hits a wide, fragmented set of personal and corporate inboxes where one complaint hurts much more than it would on a B2B account list. Starting low and earning the right to ramp is the safe default.'], - ['Should each recruiter on the team have their own warmup?', 'Yes. Each recruiter mailbox is warmed independently inside the premium pool. Warmup uses a +1/day ramp from 10/day up to a 40/day ceiling, which is separate from the cold candidate budget.'], - ['Why 3 steps and not 7?', 'Candidate outreach is permission-seeking, not pipeline-building. The first or second touch is where almost every reply lands. A long sequence keeps the same low-yield message in the mailbox longer, which gives the recipient more chances to mark it as promotional or report it as spam.'], -]; - -// Astro JSX caveat: hoist `<` and `<=` comparisons to the frontmatter where possible. -const tipsForRecruiters = [ - 'No image-heavy job description. Link to a hosted JD page instead.', - 'No HTML signature with logo, social links, or banner. Plain text only.', - 'No tracking pixel on candidate outreach.', - 'Use the recruiter\'s individual mailbox, not careers@ or talent@.', - 'Keep the first message under 90 words.', + [ + 'Why recommend about 30 a day when the platform allows more?', + 'The platform sets a 50/day default and lets you raise it up to 100. That headroom is there for high-volume sales sending to a known set of business domains, where the sender already has a track record. Recruiter outreach goes to a much wider mix of personal and work inboxes, so a single complaint carries more weight. Start near 30 and raise the cap once a mailbox has gone a few weeks without complaints or bounces, which lowers the chance a complaint forces you to pause it.', + ], + [ + 'Should each recruiter have their own warmup?', + 'Yes. Each recruiter mailbox warms up on its own in the premium pool. Warmup ramps by one message a day, from 10 a day up to a 40 a day ceiling, and runs separately from the cold sending budget, so the reputation each mailbox builds stays with that mailbox.', + ], + [ + 'Why three messages and not seven?', + 'Most candidates who reply do so on the first or second message. A longer sequence rarely adds replies. It mostly keeps a low-interest email in front of someone who has already passed, which is how spam reports build up. The third message gives an easy way to opt out, and then it stops.', + ], ]; ---