Skip to main content

Email

Pai includes a built-in email tool that lets agents send emails without any configuration. The platform holds the sending credentials; agents only supply the recipient, subject, and body.

  • Sender address is fixed — always bot@pairun.dev. Agents cannot change it.
  • Send-only — agents cannot read, list, or search email.
  • No opt-in requiredsend_email is available in every session agent and every service Agent out of the box.

Using send_email in a session agent

The send_email tool is a built-in, enabled by default — the same as bash or web_search. Just use it in your system prompt:

apiVersion: pai.io/v1
kind: Agent
metadata:
name: daily-report
spec:
models:
- anthropic/claude-sonnet-4-6
system: |
You are a daily reporting agent. Fetch the day's metrics, write a concise
summary, and email it to the address provided in the task prompt.

The agent will have access to send_email automatically. No tools: block needed.

Disable email if not needed

If your agent should not be able to send email, disable it explicitly:

tools:
- type: send_email
enabled: false

Tool reference

ParameterTypeRequiredDescription
tostringYesRecipient email address
subjectstringYesEmail subject line (max 998 chars)
bodystringYesPlain-text email body
htmlstringNoOptional HTML version. If provided, recipients with HTML-capable clients see this instead of the plain-text body
attachmentsarrayNoFiles to attach. Each entry: {path, filename?}. The harness reads the file from disk, base64-encodes, and forwards through the email service. The screenshot tool uses this internally for the email_to= flow

The tool returns "Email sent to <address> (status 202)" on success, or an [error] string on failure.

Attachments example

The agent decides to call:

send_email(
to="me@example.com",
subject="Today's report",
body="See attached.",
attachments=[{"path": "/workspace/report.pdf"}]
)

All three email backends (SMTP, Resend, SendGrid) support attachments — pick one per the Platform operator setup section.

Example A: one-shot task — CSV analysis with emailed report

This example walks through an agent that reads a CSV file, analyses it, and emails the results. No custom container image needed.

1. Create the Agent

# csv-reporter.agentdef.yaml
apiVersion: pai.io/v1
kind: Agent
metadata:
name: csv-reporter
spec:
models:
- anthropic/claude-sonnet-4-6
system: |
You are a data analysis agent. When given a CSV file path and a recipient
email address, you will:

1. Read the file with the `read` tool
2. Run Python with `bash` to compute key statistics (row count, column
summary, min/max/mean for numeric columns, top values for categoricals)
3. Write a clean markdown report to /workspace/report.md
4. Send the report as the email body using `send_email`
5. Confirm what was sent, including the file path and recipient

Format the email body in plain text with clear sections and a summary
table. Keep it under 2000 words.
packages:
pip:
- pandas
tools:
- type: bash
- type: read
- type: write
- type: send_email
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"
pai apply -f csv-reporter.agentdef.yaml

2. Run a one-shot task

pai run analyse-q1 \
--agent csv-reporter \
--task "Analyse /workspace/sales-q1.csv and send the report to finance@example.com"

3. Stream the output

pai sessions logs analyse-q1 --follow

You'll see the agent work through the steps:

[harness] session=analyse-q1 title="Analyse /workspace/sales-q1.csv..."
Reading /workspace/sales-q1.csv...
Running pandas analysis...
Writing report to /workspace/report.md...
Sending email to finance@example.com...
Email sent to finance@example.com (status 202)
Done. Report saved to /workspace/report.md and emailed to finance@example.com.

4. Check the session status

pai sessions get analyse-q1
# NAME PHASE TOKENS STARTED COMPLETED
# analyse-q1 Complete 4821 2024-03-15 09:12:01 2024-03-15 09:12:34

Example B: scheduled weekly digest

This example creates an agent that runs every Monday morning, researches a topic using web search, and emails a digest. No CSV or volume needed.

1. Create the Agent

# weekly-digest.agentdef.yaml
apiVersion: pai.io/v1
kind: Agent
metadata:
name: weekly-digest
spec:
models:
- anthropic/claude-sonnet-4-6
system: |
You are a weekly digest agent. Each time you run, you will:

1. Use web_search to find the 5 most significant recent developments on
the topic provided in the task prompt
2. Use web_fetch (max_bytes=20000) to read the 3 most relevant articles
3. Write a 400–600 word digest in this format:
Subject: Weekly Digest — {topic}{date}

TL;DR (2-3 sentences)

Top stories:
1. **Title** — one paragraph summary. Source: URL
2. ...
3. ...

Trends to watch:
- bullet point

4. Send the digest using send_email with:
- to: the recipient address from the task prompt
- subject: the subject line from the digest
- body: the full digest text

Always finish by confirming the email was sent and summarising the digest.
tools:
- type: web_search
- type: web_fetch
- type: send_email
tokens:
maxPerDay: 500000
maxPerRequest: 16000
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"

2. Create the scheduled Agent

# weekly-digest.agent.yaml
apiVersion: pai.io/v1
kind: Agent
metadata:
name: weekly-digest-job
spec:
type: task
agentDefinition: weekly-digest
schedule: "0 8 * * 1" # Every Monday at 8:00am UTC
title: >
Research the latest developments in AI infrastructure and
send a digest to team@example.com

3. Apply both resources

pai apply -f weekly-digest.agentdef.yaml
pai apply -f weekly-digest.agent.yaml
kubectl get agent weekly-digest-job
# NAME TYPE PHASE SCHEDULE LAST RUN AGE
# weekly-digest-job task Scheduled 0 8 * * 1 Complete 2d

4. Trigger a manual run to verify

kubectl create job --from=cronjob/pai-cron-weekly-digest-job \
manual-$(date +%s) -n pai-system

Then stream its logs:

# Get the job pod name
kubectl get pods -n pai-system -l job-name --sort-by=.metadata.creationTimestamp | tail -1

pai sessions logs weekly-digest-job --follow

Using send_email in a service Agent

For persistent service Agents (bring-your-own-image), the platform injects PAI_EMAIL_ENDPOINT into every agent container. Call it directly:

import os, json, urllib.request

def send_email(to: str, subject: str, body: str, html: str | None = None):
payload = json.dumps({"to": to, "subject": subject, "body": body, "html": html}).encode()
req = urllib.request.Request(
os.environ["PAI_EMAIL_ENDPOINT"],
data=payload,
headers={
"Content-Type": "application/json",
"X-Pai-Workload-Name": os.environ.get("PAI_WORKLOAD_NAME", ""),
"X-Pai-Workload-Namespace": os.environ.get("PAI_WORKLOAD_NAMESPACE", ""),
},
method="POST",
)
with urllib.request.urlopen(req, timeout=15) as resp:
return json.loads(resp.read())

The endpoint is http://pai-email.pai-system.svc.cluster.local/send — available in-cluster on all namespaces.

Rate limiting

Each workload is limited to 100 emails per day by default. This is configurable by platform operators via the PAI_EMAIL_DAILY_LIMIT environment variable on the email service deployment.

When the limit is reached, the tool returns an error and the HTTP endpoint responds with 429 Too Many Requests.

Platform operator setup

Before agents can send email, an operator must deploy the email service and configure a sending backend.

Deploy the service

kubectl apply -f platform/manifests/email-deploy.yaml

Configure a backend

SMTP (Gmail, AWS SES SMTP relay, Mailgun, Postmark, etc.):

kubectl create secret generic pai-email-smtp \
--from-literal=SMTP_HOST=smtp.example.com \
--from-literal=SMTP_PORT=587 \
--from-literal=SMTP_USER=bot@pairun.dev \
--from-literal=SMTP_PASSWORD=<password> \
--from-literal=SMTP_TLS=starttls \
-n pai-system

Resend (recommended for new deployments):

kubectl create secret generic pai-email-resend \
--from-literal=RESEND_API_KEY=re_... \
-n pai-system

Then set PAI_EMAIL_BACKEND=resend in the Deployment.

SendGrid:

kubectl create secret generic pai-email-sendgrid \
--from-literal=SENDGRID_API_KEY=SG.... \
-n pai-system

Then set PAI_EMAIL_BACKEND=sendgrid in the Deployment.

Restrict recipient domains (optional)

To prevent agents from emailing arbitrary addresses, set PAI_EMAIL_ALLOWED_DOMAINS on the Deployment:

- name: PAI_EMAIL_ALLOWED_DOMAINS
value: "example.com,corp.io"

Attempts to send to other domains return 400 Bad Request.

Metrics

The email service exposes Prometheus metrics at /metrics (port 8080):

MetricLabelsDescription
pai_emails_sent_totalworkload, backendSuccessfully sent emails
pai_emails_rejected_totalworkload, reasonRejected sends (rate limit, domain policy)
pai_email_errors_totalworkload, backendDelivery failures