Automate candidate communications at scale while keeping a human in the loop on every send — required for EEOC, FCRA, and GDPR compliance.
Recruiting teams send thousands of emails per open role: sourcing outreach, screening follow-ups, interview confirmations, rejection notices, offer letters, and onboarding packets. Most of this volume is templated, repetitive work that coordinators spend hours on each week. AI agents can handle the full sequence — but candidate-facing communication sits at the intersection of employment law, privacy regulation, and employer brand. A miscommunication in a rejection notice can create EEOC exposure. A background check adverse action notice sent without the required disclosures violates FCRA. An outreach email sent to a candidate in the EU without documented consent violates GDPR. MultiMail is built for exactly this risk profile: agents that do the drafting and sequencing work, with a human reviewing and approving every outbound message before delivery.
AI-generated rejection emails can inadvertently include language that implies age, gender, or background-based screening — creating EEOC liability. Templated rejections sent without review have no mechanism to catch this before it reaches candidates.
When a background check result triggers a rejection, FCRA requires a specific two-step adverse action process: pre-adverse notice, waiting period, then final adverse action. Automating this sequence without per-send human review makes it easy to collapse the required timing or omit mandatory disclosures.
Sourcing candidates in GDPR-regulated jurisdictions requires documented lawful basis before the first email. Cold outreach to EU candidates without consent tracking creates data protection exposure. Rejection and deletion workflows must also honor right-to-erasure requests within statutory timescales.
Offer letters are legally significant documents. Recruiting teams need to prove delivery timing, confirm the correct version was sent, and in some cases record acceptance. Email systems without delivery receipts and immutable audit logs cannot satisfy this requirement.
Scheduling coordination across multiple candidates and interviewers involves high-frequency email at low business value — exactly the task agents should own. But errors here (wrong time, wrong link, wrong interviewer name) damage candidate experience and can cascade into missed SLAs on time-to-fill metrics.
Set oversight_mode to gated_send on your recruiting mailboxes. Agents draft rejection notices, offer letters, and scheduling confirmations autonomously. Each message is queued for recruiter review via list_pending before delivery — the agent never sends directly. Recruiters approve, edit, or cancel from the approval queue without touching the drafting workflow.
For internal-only scheduling emails where the risk profile is lower — confirmations to candidates who have already accepted, calendar invites, Zoom links — agents can operate in monitored mode. Coordinators receive notifications of every send but don't need to approve each one, reducing overhead while keeping a visible audit trail.
Use read_only mailboxes for agents that monitor inbound candidate replies — detecting acceptance, declination, or questions — and route accordingly without the ability to respond. This lets you deploy inbox intelligence before you are ready to give agents send access, with zero risk of accidental outbound sends.
For the highest-risk sends — offer letters, FCRA pre-adverse and adverse action notices — set gated_all to require explicit human approval on every action, including reads of sensitive inbound responses. This creates a documented approval record for every step of the workflow, which is directly useful in employment disputes or regulatory audits.
import multimail
client = multimail.Client(api_key="mm_live_...")
"cm"># Agent drafts rejection — goes to approval queue, not delivered yet
result = client.send_email(
from_mailbox="[email protected]",
to="[email protected]",
subject="Your application for Senior Engineer — Acme Corp",
body="""Hi Sarah,
Thank you for taking the time to interview for the Senior Engineer role at Acme Corp.
After careful consideration, we have decided to move forward with another candidate.
We appreciate your interest and wish you well in your search.
Best,
Acme Recruiting Team""",
oversight_mode="gated_send",
metadata={
"candidate_id": "cand_8821",
"role_id": "req_4402",
"stage": "final_rejection"
}
)
print(f"Queued for review: {result.message_id}")
print(f"Status: {result.status}") "cm"># 'pending_approval'
Agent drafts a rejection email and submits it to the human approval queue. The recruiter sees it in list_pending before it is delivered.
import multimail
import time
from datetime import datetime, timedelta
client = multimail.Client(api_key="mm_live_...")
def send_fcra_pre_adverse(candidate_email: str, candidate_name: str, job_title: str) -> str:
"""Step 1: Pre-adverse action notice. Must wait >=5 business days before final adverse."""
result = client.send_email(
from_mailbox="[email protected]",
to=candidate_email,
subject="Important Notice Regarding Your Employment Application",
body=f"""Dear {candidate_name},
We are writing to inform you that information contained in a consumer report
may affect our hiring decision for the {job_title} position.
You have the right to dispute the accuracy or completeness of any information
in the report. Contact information for the consumer reporting agency is enclosed.
Please respond within 5 business days if you wish to dispute any information.
Human Resources
Acme Corp""",
oversight_mode="gated_all",
metadata={
"notice_type": "fcra_pre_adverse",
"notice_sent_at": datetime.utcnow().isoformat(),
"final_adverse_not_before": (datetime.utcnow() + timedelta(days=7)).isoformat()
}
)
return result.message_id
def send_fcra_final_adverse(candidate_email: str, candidate_name: str, pre_adverse_id: str) -> str:
"""Step 2: Final adverse action notice. Only send after waiting period elapses."""
result = client.send_email(
from_mailbox="[email protected]",
to=candidate_email,
subject="Final Notice: Employment Application Decision",
body=f"""Dear {candidate_name},
Following our previous notice dated [date], we have made a final decision
not to extend an employment offer based in part on information in a consumer report.
You have the right to obtain a free copy of the report within 60 days and
to dispute its accuracy directly with the consumer reporting agency.
Human Resources
Acme Corp""",
oversight_mode="gated_all",
metadata={
"notice_type": "fcra_final_adverse",
"pre_adverse_message_id": pre_adverse_id
}
)
return result.message_id
Implements the FCRA-required pre-adverse and adverse action notice sequence with enforced timing between sends and human approval at each step.
import multimail
client = multimail.Client(api_key="mm_live_...")
def process_candidate_replies(mailbox: str):
inbox = client.check_inbox(mailbox=mailbox, unread_only=True)
for email_summary in inbox.emails:
email = client.read_email(message_id=email_summary.message_id)
"cm"># Classify intent — your LLM call here
intent = classify_candidate_intent(email.body)
if intent == "offer_accepted":
client.tag_email(
message_id=email.message_id,
tags=["offer-accepted", "requires-onboarding-kickoff"]
)
notify_onboarding_team(email)
elif intent == "offer_declined":
client.tag_email(
message_id=email.message_id,
tags=["offer-declined", "pipeline-update-needed"]
)
notify_recruiter(email, urgency="high")
elif intent == "question":
client.tag_email(
message_id=email.message_id,
tags=["candidate-question", "needs-response"]
)
"cm"># Queue for human response — agent does not reply autonomously
create_response_task(email)
else:
client.tag_email(
message_id=email.message_id,
tags=["needs-review"]
)
def classify_candidate_intent(body: str) -> str:
"cm"># Your LLM classification call here
pass
process_candidate_replies(mailbox="[email protected]")
Agent reads inbound replies, tags them by candidate intent (accepted, declined, question), and routes accordingly — without sending any response autonomously.
import multimail
from typing import TypedDict
client = multimail.Client(api_key="mm_live_...")
class InterviewSlot(TypedDict):
candidate_email: str
candidate_name: str
date: str
time: str
timezone: str
format: str "cm"># 'video' | 'phone' | 'onsite'
meeting_link: str
interviewers: list[str]
def send_interview_confirmation(slot: InterviewSlot) -> str:
format_detail = (
f"Video call: {slot[&"cm">#039;meeting_link']}"
if slot[&"cm">#039;format'] == 'video'
else f"Phone: we will call you at your registered number"
)
result = client.send_email(
from_mailbox="[email protected]",
to=slot[&"cm">#039;candidate_email'],
subject=f"Interview Confirmed — {slot[&"cm">#039;date']} at {slot['time']} {slot['timezone']}",
body=f"""Hi {slot[&"cm">#039;candidate_name']},
Your interview is confirmed for {slot[&"cm">#039;date']} at {slot['time']} {slot['timezone']}.
{format_detail}
Interviewers: {&"cm">#039;, '.join(slot['interviewers'])}
If you need to reschedule, reply to this email at least 24 hours in advance.
Acme Recruiting""",
oversight_mode="monitored",
metadata={
"interview_date": slot[&"cm">#039;date'],
"interview_format": slot[&"cm">#039;format']
}
)
return result.message_id
Sends interview confirmation emails in monitored mode — agent delivers autonomously and coordinators receive notification of each send without needing to approve individually.
| Regulation | Requirement | How MultiMail helps |
|---|---|---|
| EEOC | Candidate communications must be free of language that implies discriminatory screening based on protected characteristics including age, race, gender, national origin, disability, or religion. | gated_send oversight routes all AI-generated candidate emails through human review before delivery. Recruiters can edit or reject any draft that contains problematic language. The approval queue maintains a record of what was reviewed, who approved it, and when — creating an audit trail if a hiring decision is later challenged. |
| FCRA | When a consumer report (background check) contributes to an adverse employment decision, employers must send a pre-adverse action notice, wait at least five business days, then send a final adverse action notice with specific required disclosures. | Using gated_all oversight, agents can draft both the pre-adverse and final adverse notices and queue them for HR review before sending. The metadata field on each message records notice type and timing, making it straightforward to verify the required waiting period elapsed between the two sends. |
| GDPR | Contacting candidates in EU/EEA jurisdictions requires a documented lawful basis. Candidate data must not be retained longer than necessary, and individuals have the right to request erasure of their personal data from your systems. | Mailboxes can be segmented by region, and per-mailbox oversight modes let you apply stricter review to EU candidate outreach. Inbound deletion requests can be routed via webhook to trigger automated data removal workflows. Tag and metadata fields support tracking consent status and lawful basis per candidate. |
| State Employment Laws | Several US states (California, New York, Illinois) have specific restrictions on automated employment decisions, salary history inquiries, and required disclosures in job postings and offer letters. Some require human review of AI-assisted hiring decisions. | The gated_send and gated_all oversight modes directly satisfy requirements for human review of automated hiring communications. Every send is tied to an approval record with timestamp and approver identity, which can be exported for compliance audits or in response to state agency investigations. |
Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 50-tool MCP server. Formally verified in Lean 4.