The separation of duties principle in multi-agent crews
In a multi-agent system, giving every agent direct email execution privileges violates basic least-privilege security. If a researcher agent has sending credentials, a hallucination during data gathering could trigger accidental emails.
A resilient CrewAI architecture separates responsibilities into three distinct roles: Researcher Agent → Drafting Agent → Gatekeeper & Outbound Dispatcher.
Implementing the CrewAI email tool and human sign-off task
CrewAI supports human-in-the-loop task execution natively via human_input=True on critical tasks. Here is how to configure the outbound dispatch step with SadaSend scoped keys.
from crewai import Agent, Task, Crew, Process
from crewai.tools import tool
import requests
import os
@tool("Send Outbound Email")
def send_email_tool(to_email: str, subject: str, content: str) -> str:
"""Dispatches transactional email via SadaSend API."""
res = requests.post(
"https://api.sadasend.com/v1/emails",
headers={"Authorization": f"Bearer {os.getenv('SADASEND_API_KEY')}"},
json={"to": to_email, "subject": subject, "text": content}
)
if res.status_code == 200:
return f"Email sent successfully to {to_email}"
return f"Dispatch failed ({res.status_code}): {res.text}"
# 1. Specialist Drafter (No email access)
drafter = Agent(
role="Customer Communications Specialist",
goal="Draft empathetic, accurate resolution emails for user tickets",
backstory="Expert copywriter dedicated to clear, helpful communication.",
verbose=True
)
# 2. Dispatch Gatekeeper (Holds scoped email key)
dispatcher = Agent(
role="Email Gatekeeper",
goal="Verify recipient allowlists and dispatch confirmed emails",
backstory="Strict compliance officer ensuring email accuracy.",
tools=[send_email_tool],
verbose=True
)
# Tasks with Human Review
draft_task = Task(
description="Draft a response for ticket: 'Need billing receipt for Aug 2026' to user@client.com",
expected_output="A polished email body and subject line.",
agent=drafter
)
dispatch_task = Task(
description="Review the draft and send the email to user@client.com",
expected_output="Confirmation of dispatch.",
agent=dispatcher,
human_input=True # Halts execution for human approval before sending
)
crew = Crew(
agents=[drafter, dispatcher],
tasks=[draft_task, dispatch_task],
process=Process.sequential
)
crew.kickoff()Why human-in-the-loop is mandatory for autonomous outreach
Autonomous outreach without human review leads to catastrophic deliverability loss. Spam filters at Google and Yahoo measure recipient complaint rates in real time: crossing 0.10% results in immediate throttling, while 0.30% triggers permanent domain suspension.
By inserting an explicit human confirmation barrier into CrewAI dispatch tasks, organizations protect their primary sending domains while accelerating drafting speeds by 10x.
Building AI agents that send email?
Join the SadaSend early access waitlist to get scoped API keys, recipient allowlists, and Model Context Protocol (MCP) servers upon launch.