1. Set up a workspace
Sign in with email or Google. Create a clearly named workspace, verify a domain, and create an API key. Copy the secret before leaving the page. Keep it in your server environment as AGENTMAIL_API_KEY. Never put it in browser code.
Each API key belongs to one workspace, regardless of which workspace is selected in the dashboard later.
2. Verify a domain you control
- Add a domain or subdomain under Domains.
- Publish the SenderPermit ownership TXT record shown on its card.
- Click Verify ownership & setup. Existing provider domains are linked only after ownership verification.
- Publish the sending DNS records, then click Verify DNS and Refresh status until verified.
If your DNS host automatically appends your domain, enter only the relative record name. Keep existing inbox MX records intact. Adding a sending domain does not enable receiving.
Setup failures keep your record and ownership proof for retry. Adding the same domain again in the same workspace opens its existing setup. Domains assigned to another workspace cannot be claimed here.
3. Send an email
curl 'https://senderpermit.com/api/v1/emails' \
-H "Authorization: Bearer $AGENTMAIL_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: welcome-user-123' \
-d '{"from":"team@yourdomain.com","to":"customer@example.com","subject":"Welcome","text":"Your account is ready."}'const response = await fetch('https://senderpermit.com/api/v1/emails', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.AGENTMAIL_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': 'welcome-user-123'
},
body: JSON.stringify({from: 'team@yourdomain.com', to: 'customer@example.com',
subject: 'Welcome', text: 'Your account is ready.'})
});
const result = await response.json();
if (!response.ok) throw new Error(result.error?.message || 'Send failed');
console.log(result);import json, os, urllib.request
request = urllib.request.Request(
'https://senderpermit.com/api/v1/emails',
data=json.dumps({'from': 'team@yourdomain.com', 'to': 'customer@example.com',
'subject': 'Welcome', 'text': 'Your account is ready.'}).encode(),
headers={'Authorization': 'Bearer ' + os.environ['AGENTMAIL_API_KEY'],
'Content-Type': 'application/json', 'Idempotency-Key': 'welcome-user-123'},
method='POST')
with urllib.request.urlopen(request, timeout=30) as response:
print(json.load(response))Use a unique idempotency key for each logical message; keep the same key and exact payload for retries. Change the example key for a different recipient or message.
Required fields: from, to, subject, and text. The from address must use your verified domain. The to field accepts one address or an array of 1–50 addresses. reply_to is optional. This API accepts plain text.
Newly accepted sends return HTTP 202 with id and status; successful replays return HTTP 200 without another send. Provider acceptance does not guarantee inbox delivery. Check Email logs and delivery events.
Receive email
Use a dedicated subdomain such as inbox.example.com. Choose Set up receiving in Domains and publish its MX record. This changes routing for that subdomain. Do not replace your main inbox MX records unless you intend to move that inbox.
curl 'https://senderpermit.com/api/v1/received' -H "Authorization: Bearer $AGENTMAIL_API_KEY"Returns the latest 100 inbound messages for the key’s workspace, including id, from_address, to_addresses, subject, text_body, and created_at. For AI replies, assign an active agent identity on a receiving-enabled domain.
Signed webhook events
Add a public HTTPS endpoint under Webhooks and save its one-time signing secret. Events include email.received, email.sent, email.delivered, email.bounced, and email.failed. They can arrive more than once or out of order.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyWebhook(headers, rawBody, secret) {
const id = headers.get('webhook-id');
const timestamp = headers.get('webhook-timestamp');
const signature = headers.get('webhook-signature') || '';
if (!id || !/^\d+$/.test(timestamp || '')) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
if (!/^v1=[a-f0-9]{64}$/.test(signature)) return false;
const expected = createHmac('sha256', secret)
.update(id + '.' + timestamp + '.' + rawBody).digest();
const received = Buffer.from(signature.slice(3), 'hex');
return received.length === expected.length && timingSafeEqual(received, expected);
}
// Read rawBody with await request.text() BEFORE JSON parsing.
// Reject invalid signatures. Persist and deduplicate webhook-id before processing.
// Return 2xx only after durable acceptance; queue slow work.Use the exact raw body. Reject timestamps outside five minutes and invalid signatures. Store webhook-id in a durable unique column to prevent repeated business actions.
Failures retry with backoff up to six attempts. Background processing is scheduled every five minutes, with possible delays. Inspect and retry deliveries under Webhooks. Alerts flag failed deliveries and pending deliveries older than 15 minutes.
AI agents, policies, and approvals
- Create an agent with clear instructions.
- Assign its dedicated email identity on a receiving-enabled domain.
- Arrange an AI drafting allowance through support. API email plans do not include AI drafting.
- Send a message to the identity and inspect its conversation.
- Approve & send or Reject the draft in Approvals. Inspect Actions and Audit trail for the result.
No matching policy requires human approval. Rules run in priority order, lowest number first; the first matching rule wins. Block prevents sending; allow can send automatically. Use an exact recipient for controlled tests. Instructions alone never grant permission.
Paused agents or identities hold messages. Resume them before Retry processing in Actions. A held-message alert is not necessarily a delivery failure.
Errors and safe retries
| HTTP | Next step |
|---|---|
| 400 | Check required fields and format. |
| 401 | Use a valid, unrevoked Bearer key. |
| 403 | Verify domain ownership in the key’s workspace. |
| 409 | Check changed payload, in-progress send, or expired retry window. |
| 429 | Check your monthly allowance. |
| 500 / 502 | Inspect Email logs before retrying with the same key. |
After a timeout, wait at least three minutes and retry the same payload and key. The provider may already have accepted it. The safe retry window is 23 hours; after that, check delivery before creating another message.
Developer includes 100 API recipients monthly; Starter includes 10,000 for $20/month. Allowances reset on the first day of each month in UTC. See pricing →
Contact support →