Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions python/src/agentmail_toolkit/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from agentmail import AgentMail
from agentmail.inboxes import CreateInboxRequest

from .schemas import CreateDomainParams

import io
import logging
import filetype
Expand Down Expand Up @@ -52,6 +54,28 @@ def delete_inbox(client: AgentMail, kwargs: Kwargs):
return client.inboxes.delete(**kwargs)


def list_domains(client: AgentMail, kwargs: Kwargs):
return client.domains.list(**kwargs)


def get_domain(client: AgentMail, kwargs: Kwargs):
return client.domains.get(**kwargs)


def create_domain(client: AgentMail, kwargs: Kwargs):
# Parsed so only the fields this tool offers reach the SDK, whose create also takes
# allow_conflicting_provider; the OpenAI and LiveKit adapters hand over raw arguments.
return client.domains.create(**CreateDomainParams(**kwargs).model_dump(exclude_none=True))


def get_domain_setup_link(client: AgentMail, kwargs: Kwargs):
return client.domains.get_setup_link(**kwargs)


def verify_domain(client: AgentMail, kwargs: Kwargs):
return client.domains.verify(**kwargs)


def list_threads(client: AgentMail, kwargs: Kwargs):
return client.inboxes.threads.list(**kwargs)

Expand Down
23 changes: 23 additions & 0 deletions python/src/agentmail_toolkit/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
ThreadIdField = Annotated[str, Field(description="ID of thread")]
MessageIdField = Annotated[str, Field(description="ID of message")]
AttachmentIdField = Annotated[str, Field(description="ID of attachment")]
DomainIdField = Annotated[str, Field(description="ID of domain (from list_domains or create_domain)")]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field description (nit). get_domain also returns the ID, not only list_domains and create_domain.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving as is. The description names where an agent gets an ID it does not have yet. Calling get_domain already needs the ID, so it is not a source.



class ListItemsParams(BaseModel):
Expand All @@ -25,6 +26,28 @@ class CreateInboxParams(BaseModel):
display_name: Optional[str] = Field(default=None, description="Display name")


class GetDomainParams(BaseModel):
domain_id: DomainIdField


# allow_conflicting_provider is not offered: every domain made here receives mail, so its MX record is
# required, and a domain cannot receive at AgentMail and at Google Workspace or Microsoft 365 at once.
# With the flag the agent could only stop the user's current mail or end up receiving nothing.
class CreateDomainParams(BaseModel):
domain: str = Field(description="Domain name, e.g. example.com or mail.example.com")
feedback_enabled: Optional[bool] = Field(
default=None, description="Send bounce and complaint notifications to your inboxes. Default true"
)
subdomains_enabled: Optional[bool] = Field(
default=None,
description="Allow inboxes on any subdomain of this domain. Adds a required wildcard MX record. Default false",
)
tracking_enabled: Optional[bool] = Field(
default=None,
description="Serve open tracking from this domain. Adds a required link CNAME record. Default false",
)


class ListInboxItemsParams(ListItemsParams):
inbox_id: InboxIdField
labels: Optional[List[str]] = Field(default=None, description="Filter items with labels")
Expand Down
70 changes: 70 additions & 0 deletions python/src/agentmail_toolkit/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
ListInboxItemsParams,
GetInboxParams,
CreateInboxParams,
GetDomainParams,
CreateDomainParams,
GetThreadParams,
GetAttachmentParams,
SendMessageParams,
Expand All @@ -22,6 +24,11 @@
get_inbox,
create_inbox,
delete_inbox,
list_domains,
get_domain,
create_domain,
get_domain_setup_link,
verify_domain,
list_threads,
get_thread,
get_attachment,
Expand Down Expand Up @@ -66,6 +73,69 @@ class Tool(BaseModel):
params_schema=GetInboxParams,
func=delete_inbox,
),
Tool(
name="list_domains",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation, not a defect. list_domains uses ListItemsParams, so the SDK's ascending is not exposed. That matches list_inboxes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, a deliberate match with list_inboxes. No change.

description=(
"List custom email domains, paginated. Use get_domain for a domain's verification status and "
"DNS records. Requires the domain_read permission."
),
params_schema=ListItemsParams,
func=list_domains,
),
Tool(
name="get_domain",
description=(
"Get a custom email domain by ID: its verification status, the reason it is not verified yet, "
"and the DNS records to add at the domain's DNS provider, each with its own status and reason. "
"Requires the domain_read permission; inbox-scoped API keys cannot access domains."
),
params_schema=GetDomainParams,
func=get_domain,
),
Tool(
name="create_domain",
description=(
"Add a custom email domain. Returns it with status NOT_STARTED and the DNS records to add at the "
"domain's DNS provider. Next, call get_domain_setup_link for a one-click link that adds them, or "
"add them by hand, then call verify_domain. AgentMail receives the domain's mail, so adding its "
"MX record moves mail away from any provider the domain uses today. Fails with 422 when the "
"domain already receives mail through Google Workspace or Microsoft 365: suggest a subdomain such "
"as agents.example.com instead. Requires the domain_create permission."
),
params_schema=CreateDomainParams,
func=create_domain,
),
Tool(
name="get_domain_setup_link",
description=(
"Get a one-click link that adds a domain's DNS records at its DNS provider, when the provider "
"supports it (for example Cloudflare or Vercel). When supported is true, open url in a browser: "
"the domain owner signs in at provider_name, reviews the records and approves, and the provider "
"writes them; the browser then lands on the AgentMail console. When get_domain lists an MX "
"record, the link replaces the domain's current MX records, and mail to any provider the domain "
"uses today stops. conflicting_provider names such a provider when the check finds one, but the "
"check can miss it, so confirm with the user that the domain has no other mail provider before "
"opening such a link. When supported is false, add the records from get_domain by hand. Once the "
"records are in place, call verify_domain. Requires the domain_read permission."
),
params_schema=GetDomainParams,
func=get_domain_setup_link,
),
Tool(
name="verify_domain",
description=(
"Start verifying a domain once its DNS records are in place. Returns at once and the check runs "
"in the background: call get_domain to follow it until status is VERIFIED. Until then, the "
"domain's reason says what is left: a dns_records_* reason means a record is missing or wrong at "
"the DNS provider (each record has its own status and reason); ses_*_pending and "
"ses_*_temporary_failure clear on their own; ses_*_failed and ses_*_not_started need "
"verify_domain again once the records are right. DNS changes can take a while to be seen, so if "
"nothing changes after several checks, stop and tell the user the reason. Requires the "
"domain_update permission."
),
params_schema=GetDomainParams,
func=verify_domain,
),
Tool(
name="list_threads",
description="List threads in inbox",
Expand Down
Loading
Loading