CrewAI structures work as a set of role-based agents collaborating on tasks, which makes an inbox a natural fit for exactly one of those roles — a support responder, an outreach agent, whatever role in your crew should actually be allowed to send mail. As with LangChain, there's no official CrewAI package for this: DimeBox doesn't ship a CrewAI tool, and nothing here should be read as implying one exists. What follows is a tool built the same way you'd build a tool around any other HTTP API.
A crew sharing one inbox across several agents is a good case for a card-funded wallet instead of raw x402 signing: fund the wallet once, and every tool call just needs Authorization: Bearer wsec_... rather than a per-call signature and a registered EVM scheme. That's less infrastructure sitting inside a tool whose actual job is "send this email," and it means no agent in the crew needs direct access to a signer. Read how wallet secrets work before deciding where that secret actually lives — it's the one credential anyone with access to the crew's environment can use to spend.
CrewAI tools subclass BaseTool with a Pydantic schema describing the arguments the agent can pass:
import httpx
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
class SendEmailInput(BaseModel):
to: str = Field(..., description="Recipient address")
purpose: str = Field(..., description="What the email needs to accomplish")
class SendInboxEmailTool(BaseTool):
name: str = "send_inbox_email"
description: str = "Send an email from the crew's shared DimeBox inbox. Costs money per call."
args_schema: type[BaseModel] = SendEmailInput
def _run(self, to: str, purpose: str) -> str:
resp = httpx.post(
"https://dimebox.app/api/inboxes/acme-support/send",
json={"to": to, "purpose": purpose},
headers={"Authorization": "Bearer wsec_..."},
)
resp.raise_for_status()
return f"queued: {resp.json()}"
class CheckInboxTool(BaseTool):
name: str = "check_inbox"
description: str = "Read the crew's shared inbox for new messages. Free."
def _run(self) -> str:
resp = httpx.get(
"https://dimebox.app/api/inboxes/acme-support/messages",
headers={"X-Inbox-Token": "inbox_..."},
)
resp.raise_for_status()
return str(resp.json())
Test the tool class on its own, outside the crew entirely, before trusting a role to call it — instantiate it directly and call _run with known-good arguments. A mistake in the request shape is much easier to see in a plain Python traceback than buried in a crew's reasoning trace, where a failed tool call just looks like one more thing the agent tried.
A malformed request to /send — a missing to, a purpose sent to an inbox provisioned without AI drafting — comes back as a 422, and that response is never charged even on a route that normally costs money. Let httpx.HTTPStatusError propagate a useful message rather than swallowing it: CrewAI feeds a tool's return value (or raised error) back into the agent's reasoning, so a clear "recipient address was missing" gives the model something to correct on the next attempt, where a bare stack trace doesn't.
Attach the send tool only to the agent whose job actually requires it:
from crewai import Agent
support_responder = Agent(
role="Support Responder",
goal="Resolve inbound customer questions by email",
backstory="Handles the crew's external correspondence.",
tools=[SendInboxEmailTool(), CheckInboxTool()],
)
researcher = Agent(
role="Researcher",
goal="Gather information the responder needs",
backstory="Feeds context to the responder; never contacts customers directly.",
tools=[], # no email access
)
This is least-privilege applied to a crew: a researcher that never needed to send mail can't accidentally send mail, because it was never given the tool in the first place. It's a cheaper and more reliable guard than trying to prompt your way around a capability the model technically has.
Pair CheckInboxTool with a task that runs after the responder sends mail, so a later crew run can act on whatever came back. Building an agent that triages its own inbox covers the pattern for turning raw inbound messages into something a crew can act on, rather than dumping JSON into context and hoping the model does something sensible with it.
If the environment running your crew already manages its own EVM signer for other reasons, there's nothing stopping you from using it here too — swap the bearer header for the same 402-sign-retry loop shown in the Python quickstart, inside the same _run method. The tool interface doesn't care which payment method sits behind it; CrewAI only ever sees a function that returns a string.
Either way, start from the quickstart to confirm the raw calls work before wrapping them in a tool — debugging a 402 inside a crew's tool-call logs is much harder than debugging it at a bare curl command.