01 / FIRST DELIVERY
Quickstart
- Create and activate a project.Each project has its own quota, senders, templates, keys, and ledger.
- Approve the From address.Add the exact sender address under Senders and keep it active.
- Create an API key.Copy it immediately. Phefo stores only its hash and cannot reveal it later.
- Call the API from your server.Never place a project key in browser or mobile application code.
PHEFO_MAIL_BASE_URL=https://mail.web.co.lsPHEFO_MAIL_API_KEY=pm_…PHEFO_MAIL_FROM=receipts@your-domain.comcurl --request POST 'https://mail.web.co.ls/api/v1/messages' \
--header 'Authorization: Bearer $PHEFO_MAIL_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"from": "receipts@your-domain.com",
"to": "customer@example.com",
"subject": "Your receipt",
"text": "Thanks for your order.",
"html": "<p>Thanks for your order.</p>",
"idempotencyKey": "order-1042-receipt-v1"
}'02 / APPLICATION ISOLATION
One key per application
Give every application a separately named API key. Keys can be revoked independently, while the project keeps a shared sender allowlist and delivery ledger.
A key does not grant permission to impersonate arbitrary addresses. Every exact from address must be active in the project’s Senders list.
03 / DIRECT CONTENT
Send contract
Node / TypeScript
const response = await fetch(
process.env.PHEFO_MAIL_BASE_URL + '/api/v1/messages',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PHEFO_MAIL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: process.env.PHEFO_MAIL_FROM,
to: 'customer@example.com',
subject: 'Your receipt',
text: 'Thanks for your order.',
html: '<p>Thanks for your order.</p>',
idempotencyKey: 'order-1042-receipt-v1',
}),
},
)
const result = await response.json()
if (!response.ok) throw new Error(result.error?.code || 'PHEFO_MAIL_ERROR')
console.log(result.data) // { id, status: 'queued' }Python
import os
import requests
response = requests.post(
os.environ["PHEFO_MAIL_BASE_URL"] + "/api/v1/messages",
headers={
"Authorization": f"Bearer {os.environ['PHEFO_MAIL_API_KEY']}",
"Content-Type": "application/json",
},
json={
"from": os.environ["PHEFO_MAIL_FROM"],
"to": "customer@example.com",
"subject": "Your receipt",
"text": "Thanks for your order.",
"idempotencyKey": "order-1042-receipt-v1",
},
timeout=15,
)
response.raise_for_status()
print(response.json()["data"])fromemailRequired. Must be an approved sender.
toemail | email[]Required. One to 100 recipients.
cc / bccemail | email[]Optional. Up to 100 each.
replyToemailOptional reply destination.
subjectstringRequired without a template; max 998 characters.
html / textstringAt least one required without a template.
idempotencyKeystringRecommended; stable per event, 8–160 characters.
04 / REUSABLE CONTENT
Templates
Create templates in the dashboard and send their ID with primitive template data. Variables use escaped {{name}} placeholders. Helpers, expressions, raw triple braces, and HTML/text overrides are rejected.
{
"from": "hello@your-domain.com",
"to": "customer@example.com",
"templateId": 7,
"templateData": {
"name": "Mpho",
"orderNumber": 1042
},
"idempotencyKey": "order-1042-welcome-v1"
}05 / FILES
Attachments
Send up to ten base64-encoded files. The decoded size and MIME type must satisfy the current server settings. Common defaults include PDF, PNG, JPEG, plain text, and CSV.
{
"from": "reports@your-domain.com",
"to": "customer@example.com",
"subject": "Your monthly report",
"text": "The report is attached.",
"attachments": [
{
"filename": "report.pdf",
"contentType": "application/pdf",
"contentBase64": "JVBERi0xLjQK..."
}
]
}06 / DELIVERY STATE
Responses and retries
{"data":{"id":42,"status":"queued"}}{
"data":{"id":42,"status":"sent"},
"meta":{"idempotentReplay":true}
}Queued means durably accepted by Phefo. Sent means the SMTP server accepted at least one recipient; it is not guaranteed inbox delivery. Use the dashboard ledger for operational status. This distinction follows the separation between message submission and later transfer described by the SMTP standard.
07 / FAILURE MODES
Structured errors
{
"error": {
"code": "SENDER_NOT_ALLOWED",
"message": "The from address is not approved",
"details": {}
}
}INVALID_API_KEY401Missing, revoked, or invalid project key.SENDER_NOT_ALLOWED403The From address is not active on this project.PROJECT_INACTIVE403The project is pending or suspended.TEMPLATE_NOT_FOUND404The template does not belong to this project.VALIDATION_ERROR422The JSON payload does not match the send contract.ATTACHMENT_TYPE_NOT_ALLOWED422The MIME type is not allowed by server settings.ATTACHMENT_SIZE_INVALID422The decoded file is empty or over the configured limit.RATE_LIMITED429The API key exceeded its per-minute limit.DAILY_QUOTA_EXCEEDED429The project reached its daily sending quota.08 / HAND THIS TO YOUR CODING AGENT
Full integration prompt
Integrate this application with Phefo Mail, a transactional email HTTP API.
Production endpoint:
POST https://mail.web.co.ls/api/v1/messages
Implementation requirements:
1. Perform the request only from trusted server-side code. Never expose the API key in browser code, logs, exceptions, or source control.
2. Read configuration from:
- PHEFO_MAIL_BASE_URL=https://mail.web.co.ls
- PHEFO_MAIL_API_KEY=<secret project key>
- PHEFO_MAIL_FROM=<approved sender address>
3. Send the key as: Authorization: Bearer <PHEFO_MAIL_API_KEY>
4. Send JSON with "from", "to", and either:
- direct content: "subject" plus "text" and/or "html"; or
- "templateId" plus optional primitive "templateData".
5. Generate a stable, business-specific idempotencyKey (8–160 characters) for every transactional event so retries cannot create duplicate email.
6. Treat HTTP 202 with {"data":{"id":...,"status":"queued"}} as accepted into the durable queue, not proof of inbox delivery.
7. Parse errors as {"error":{"code","message","details?"}}.
8. Retry only transient failures (HTTP 429 or 5xx) with capped exponential backoff and jitter. Do not retry 401, 403, or 422 without correcting configuration or input.
9. Keep recipient data and message bodies out of application logs. Log only a safe internal event ID, Phefo message ID, HTTP status, and error code.
10. Add typed request/response models, a small reusable mail client, request timeout handling, and tests that mock the HTTP boundary.
11. If this is a browser-only application, create a server endpoint/function that calls Phefo Mail; do not call Phefo Mail directly from the browser.
12. Use the approved sender exactly as configured. A different From address will return SENDER_NOT_ALLOWED until it is approved in the Phefo Mail dashboard.
Contract details:
- "to", "cc", and "bcc" accept one email or an array (up to 100 recipients per field).
- "replyTo" is optional.
- Subject maximum: 998 characters.
- HTML/text maximum: 2,000,000 characters each.
- Up to 10 attachments can be supplied as filename, contentType, and contentBase64.
- Template messages cannot also supply html or text.
- Simple template variables use escaped {{name}} placeholders only.
Before finishing:
- Validate required environment variables at process startup.
- Show the exact files changed.
- Run the relevant tests and typecheck/build.
- Provide one safe curl smoke-test command using placeholders, never a real key.