
smolagents email tool setup: give your agent the ability to send email
Step-by-step guide to building a custom email tool in smolagents. Covers the Tool subclass, credential handling, and three different email backends.
Most smolagents tutorials stop at web search and calculator tools. Fair enough, those are easy to demo. But the moment your agent needs to actually do something in the real world, like send a confirmation, forward a summary, or reply to a customer, it needs email.
The problem is that smolagents doesn't ship with a built-in email tool. You have to build one yourself. That's not as hard as it sounds, but there are a few decisions to make about credential handling, which email backend to use, and how to structure the tool so your agent can call it reliably.
Here's how to set the whole thing up from scratch.
What is smolagents?#
Smolagents is Hugging Face's lightweight Python framework for building AI agents that write and execute code to accomplish tasks. Unlike heavier frameworks, smolagents keeps things minimal: you define tools as Python classes, hand them to an agent, and the agent writes code that calls those tools to solve problems.
The framework supports two agent types. CodeAgent writes and runs Python code directly, choosing which tools to call and how to combine their outputs. ToolCallingAgent uses structured tool-calling APIs instead. For an email tool, either works fine, but CodeAgent gives you more flexibility when composing email content from other tool outputs.
How to set up an email tool in smolagents (step by step)#
- Install smolagents with
pip install smolagents - Create a new class that inherits from
smolagents.Tool - Define the required attributes:
name,description,inputs, andoutput_type - Implement the
forward()method with your email-sending logic (SMTP or API) - Load credentials from environment variables, never hardcode them
- Instantiate your agent with the tool:
CodeAgent(tools=[YourEmailTool()], model=...) - Run the agent with a prompt that requires sending email
Each of those steps hides some real decisions. Let me walk through them.
Building the Tool subclass#
Every smolagents tool follows the same structure. You subclass Tool, set four attributes, and implement forward(). Here's a working email tool using Python's built-in smtplib:
import os
import smtplib
from email.mime.text import MIMEText
from smolagents import Tool
class EmailTool(Tool):
name = "send_email"
description = "Sends an email to a specified recipient with a subject and body."
inputs = {
"to": {"type": "string", "description": "Recipient email address"},
"subject": {"type": "string", "description": "Email subject line"},
"body": {"type": "string", "description": "Plain text email body"},
}
output_type = "string"
def forward(self, to: str, subject: str, body: str) -> str:
smtp_host = os.environ["SMTP_HOST"]
smtp_port = int(os.environ.get("SMTP_PORT", "587"))
smtp_user = os.environ["SMTP_USER"]
smtp_pass = os.environ["SMTP_PASS"]
from_addr = os.environ.get("FROM_ADDRESS", smtp_user)
msg = MIMEText(body)
msg["Subject"] = subject
msg["From"] = from_addr
msg["To"] = to
with smtplib.SMTP(smtp_host, smtp_port) as server:
server.starttls()
server.login(smtp_user, smtp_pass)
server.send_message(msg)
return f"Email sent to {to} with subject: {subject}"
The description attribute matters more than you'd think. The agent reads it to decide when to use the tool, so write it like you're explaining the tool to a coworker, not writing a docstring for a linter.
The inputs dictionary defines what arguments the agent can pass. Each key needs a type and description. Smolagents supports "string", "integer", "number", and "boolean" types. For email, strings cover everything you need.
The forward() method is where the actual work happens. Whatever it returns gets passed back to the agent as the tool's output. Return something useful: a confirmation message, a message ID, or an error description. Don't return None.
Handling credentials safely#
Hardcoding SMTP passwords in a tool class is a fast way to leak credentials into agent-generated code logs. Always use environment variables.
Set them before running your agent:
export SMTP_HOST="smtp.gmail.com"
export SMTP_PORT="587"
export SMTP_USER="you@gmail.com"
export SMTP_PASS="your-app-password"
If you're using Gmail, you'll need an App Password (not your regular password). Go to your Google Account security settings, enable 2-Step Verification, then generate an App Password under "App passwords." Regular Gmail passwords won't work with SMTP since Google disabled "less secure app access" back in 2022.
For production agents, consider a secrets manager or .env file loaded with python-dotenv. The point is to keep credentials out of any file the agent might read or log.
Three email backend options compared#
Raw SMTP isn't your only choice. Here's how the three main approaches compare for agent workflows:
| Approach | Setup effort | Deliverability | Receive support | Best for |
|---|---|---|---|---|
| Raw SMTP (Gmail, Fastmail) | Medium | Depends on provider | No (send only) | Quick prototypes |
| Transactional API (SendGrid, Mailgun, Postmark) | Medium | High (pre-warmed IPs) | Webhooks only | Production sending |
| Agent-first email (LobsterMail) | Low | Managed for you | Yes (native polling) | Agents that need to send and receive |
With a transactional API, your forward() method calls an HTTP endpoint instead of opening an SMTP connection. Here's what that looks like with requests:
import os
import requests
from smolagents import Tool
class EmailAPITool(Tool):
name = "send_email"
description = "Sends an email via a transactional email API."
inputs = {
"to": {"type": "string", "description": "Recipient email address"},
"subject": {"type": "string", "description": "Email subject line"},
"body": {"type": "string", "description": "Plain text email body"},
}
output_type = "string"
def forward(self, to: str, subject: str, body: str) -> str:
api_key = os.environ["EMAIL_API_KEY"]
from_addr = os.environ["FROM_ADDRESS"]
resp = requests.post(
"https://api.your-email-provider.com/v1/send",
headers={"Authorization": f"Bearer {api_key}"},
json={
"from": from_addr,
"to": to,
"subject": subject,
"text": body,
},
)
resp.raise_for_status()
return f"Email sent to {to}, status {resp.status_code}"
The API approach skips TLS negotiation and SMTP handshakes, which makes it faster and more reliable in serverless or containerized environments where outbound port 587 might be blocked.
Registering the tool and running your agent#
Once your tool class is ready, pass it to an agent:
from smolagents import CodeAgent, InferenceClientModel
model = InferenceClientModel(model_id="Qwen/Qwen3-32B")
agent = CodeAgent(
tools=[EmailTool()],
model=model,
)
agent.run("Send an email to hello@example.com summarizing today's top 3 HN stories")
The agent will write Python code that calls send_email(to=..., subject=..., body=...) as part of its execution. If you've given it other tools (like a web search tool), it can gather information first, then compose and send the email in the same run.
Handling failures and retries#
SMTP connections fail. APIs return 429s. Your tool needs to handle this gracefully, because the agent won't know how to retry on its own.
Wrap your sending logic in a try/except and return a clear error message:
def forward(self, to: str, subject: str, body: str) -> str:
try:
... sending logic ...#
return f"Email sent to " except smtplib.SMTPAuthenticationError: return "ERROR: SMTP authentication failed. Check credentials." except smtplib.SMTPRecipientsRefused: return f"ERROR: Recipient was refused by the server." except Exception as e: return f"ERROR: Failed to send email: "
When `CodeAgent` sees an error string come back, it can decide to retry, adjust the recipient, or report the failure. If your `forward()` raises an unhandled exception instead, the entire agent run crashes.
## What about receiving email?
This is where things get more interesting. Smolagents tools can do anything Python can do, so building a "check inbox" tool follows the same pattern. You'd use IMAP, or call an API that supports inbox polling.
If your agent needs both sending and receiving (signing up for services, reading confirmation codes, replying to threads), you're essentially building a full email client inside your tool classes. That's doable, but it's a lot of surface area to maintain. Agent-first email providers like LobsterMail handle both directions through a single SDK, which keeps your tool code simple. If you'd rather skip the SMTP and IMAP plumbing, <InlineGetStarted>check out LobsterMail for a simpler path</InlineGetStarted>.
## Deliverability considerations for agent-sent email
An agent can send hundreds of emails per hour without breaking a sweat. That's the problem. Email providers (Gmail, Outlook, Yahoo) track sender reputation at the IP and domain level. A brand-new domain blasting 500 messages on its first day will get flagged immediately.
If you're running your own SMTP, you need SPF, DKIM, and DMARC records configured on your sending domain. Without them, most recipients will reject or spam-folder your messages. Transactional email APIs handle this for you on their shared or dedicated IPs, which is one of their main selling points.
Rate limiting matters too. Add a simple sleep or queue mechanism in your tool if the agent might send more than a few messages per run.
<FAQ>
<FAQItem question="What Python packages do I need to install for a smolagents email tool?">
You need `smolagents` (`pip install smolagents`). For SMTP sending, Python's built-in `smtplib` and `email` modules are sufficient. If you're using an HTTP email API, add `requests`.
</FAQItem>
<FAQItem question="How do I subclass the smolagents Tool class to create a custom email tool?">
Create a class that inherits from `smolagents.Tool`, define `name`, `description`, `inputs`, and `output_type` as class attributes, then implement the `forward()` method with your sending logic.
</FAQItem>
<FAQItem question="What attributes must a smolagents Tool subclass define?">
Four required attributes: `name` (string identifier), `description` (tells the agent when to use it), `inputs` (dict mapping parameter names to type and description), and `output_type` (the return type, usually `"string"`).
</FAQItem>
<FAQItem question="How do I securely pass SMTP credentials to a smolagents email tool?">
Use environment variables loaded with `os.environ` inside your `forward()` method. Never hardcode passwords in the tool class. For production, use `python-dotenv` or a secrets manager.
</FAQItem>
<FAQItem question="Can I use Gmail SMTP with smolagents?">
Yes, but you need an App Password. Enable 2-Step Verification on your Google account, then generate an App Password under security settings. Regular Gmail passwords won't work with SMTP.
</FAQItem>
<FAQItem question="What is the difference between CodeAgent and ToolCallingAgent in smolagents?">
`CodeAgent` writes and executes Python code that calls your tools directly. `ToolCallingAgent` uses structured tool-calling APIs. CodeAgent is more flexible for composing multi-step workflows. Both work with custom email tools.
</FAQItem>
<FAQItem question="How do I register a custom email tool with a smolagents agent?">
Pass an instance of your tool class in the `tools` list when creating the agent: `CodeAgent(tools=[EmailTool()], model=your_model)`.
</FAQItem>
<FAQItem question="Can smolagents send HTML emails?">
Yes. Replace `MIMEText(body)` with `MIMEText(body, "html")` in your tool's `forward()` method. If using an API backend, set the `html` field instead of `text` in your request payload.
</FAQItem>
<FAQItem question="How should an email tool's forward() method handle errors?">
Wrap your sending logic in try/except and return a descriptive error string. Don't raise unhandled exceptions, because that crashes the entire agent run. Return messages like `"ERROR: SMTP authentication failed"` so the agent can react.
</FAQItem>
<FAQItem question="Can I use a transactional email API instead of raw SMTP with smolagents?">
Yes. Replace the SMTP logic in `forward()` with an HTTP request to your email API (SendGrid, Mailgun, Postmark, or [LobsterMail](/)). This is often more reliable in serverless environments where outbound SMTP ports may be blocked.
</FAQItem>
<FAQItem question="Why is my smolagents email tool not being called by the agent?">
Check your tool's `description` attribute. The agent uses it to decide when a tool is relevant. If the description is vague or doesn't mention "email" or "send," the agent may not recognize it as the right tool for email-related prompts.
</FAQItem>
<FAQItem question="Can I chain an email tool with other tools in the same smolagents agent run?">
Yes. `CodeAgent` can call multiple tools in sequence. For example, it can use a web search tool to gather information, then call your email tool to send a summary. The agent writes the glue code automatically.
</FAQItem>
<FAQItem question="What are the rate limiting considerations when an AI agent sends email?">
Email providers track sender reputation. A new domain sending hundreds of messages immediately will get flagged. Add rate limiting in your tool (a simple `time.sleep()` between sends), warm up new domains gradually, and make sure SPF, DKIM, and DMARC records are configured.
</FAQItem>
</FAQ>


