Email API
Send transactional and marketing emails to users via the stemp platform.
Email API
The Platform Email API lets your installed app send emails to an organization's users and members through stemp's email infrastructure. Emails are sent from the organization's branded address with built-in unsubscribe management and delivery logging.
Required scope: platform:email:send
Authentication: App installation tokens only
Send an Email
curl -X POST https://api.stemp.app/api/v1/email/send \
-H "Authorization: Bearer <app_installation_token>" \
-H "Content-Type: application/json" \
-d '{
"to": "usr_xyz789",
"subject": "Your reward is ready!",
"htmlBody": "<h1>Congratulations!</h1><p>You earned a free coffee.</p>",
"category": "transactional"
}'Request Body
| Field | Required | Description |
|---|---|---|
to | Yes | Recipient ID — a user (usr_xxx) or member (mem_xxx) TypeId |
subject | Yes | Email subject line |
htmlBody | Yes | HTML content of the email |
textBody | No | Plain text version (auto-generated from HTML if omitted) |
category | Yes | Email category (see Categories below) |
attachments | No | Array of inline CID attachments (see Attachments) |
Response
{
"messageId": "01234567-89ab-cdef-0123-456789abcdef",
"status": "sent"
}Recipient Resolution
The to field accepts stemp TypeId references, not raw email addresses:
usr_xxx— End user (customer) created via the Users APImem_xxx— Organization member (staff)
The API resolves the TypeId to the recipient's email address. If the recipient is not found, you'll receive a 400 Bad Request error with code platform.email.user_not_found or platform.email.member_not_found.
Email Categories
Every email must specify a category that determines delivery behavior and unsubscribe handling:
| Category | Value | Unsubscribable | Description |
|---|---|---|---|
| Transactional | transactional | No | Order confirmations, receipts, password resets |
| Security | security | No | Security alerts, login notifications |
| Marketing | marketing | Yes | Promotions, offers, campaigns |
| Product Updates | product_updates | Yes | Feature announcements, changelog |
| General | general | Yes | General communications |
Unsubscribable Categories
For categories marked as unsubscribable (marketing, product_updates, general):
- stemp automatically adds
List-Unsubscribeheaders (RFC 8058 compliant) - Recipients can opt out via one-click unsubscribe in their email client
- If a recipient has opted out, the API returns a
422 Unprocessable Entity— your app should handle this gracefully - Transactional and security emails are always delivered regardless of preferences
Inline Attachments
You can embed images directly in your HTML using CID (Content-ID) attachments:
{
"to": "usr_xyz789",
"subject": "Your stamp card is complete!",
"htmlBody": "<h1>Congratulations!</h1><img src=\"cid:reward-image\" />",
"category": "transactional",
"attachments": [
{
"cid": "reward-image",
"filename": "reward.png",
"contentType": "image/png",
"base64Content": "iVBORw0KGgoAAAANSUhEUg..."
}
]
}| Field | Required | Description |
|---|---|---|
cid | Yes | Content-ID referenced in HTML via cid: |
filename | Yes | Attachment filename |
contentType | Yes | MIME type (e.g., image/png, image/jpeg) |
base64Content | Yes | Base64-encoded file content |
Email Logs
Retrieve the sending history for your app installation:
curl "https://api.stemp.app/api/v1/email/logs?page=0&size=50" \
-H "Authorization: Bearer <app_installation_token>"Response
{
"content": [
{
"id": "eml_abc123",
"recipientEmail": "jane@example.com",
"subject": "Your reward is ready!",
"status": "SENT",
"messageId": "01234567-89ab-cdef-0123-456789abcdef",
"errorMessage": null,
"category": "transactional",
"createdAt": "2026-01-15T10:30:00Z"
}
],
"page": { "number": 0, "size": 50, "totalElements": 1, "totalPages": 1 }
}| Field | Description |
|---|---|
status | SENT or FAILED |
messageId | Delivery tracking ID (null if failed) |
errorMessage | Error details (null if successful) |
category | The email category used |
Logs are scoped to your app installation — you only see emails sent by your app.
Sender Address
Emails are sent from the organization's branded address:
From: Acme Coffee <acme-coffee@stemp.email>The format is {organization_name} <{org_slug}@stemp.email>. This is managed automatically — your app does not control the sender address.
Error Handling
| Status | Code | Description |
|---|---|---|
400 | platform.email.user_not_found | User TypeId not found |
400 | platform.email.member_not_found | Member TypeId not found |
400 | platform.email.invalid_recipient_format | Invalid TypeId format |
422 | — | Recipient opted out of this email category |
Example: Stamp Reward Notification
A complete example of sending a reward notification when a stamp card is completed:
import { Stemp } from '@stemp/node'
const stemp = new Stemp({ apiKey: process.env.STEMP_API_KEY! })
async function notifyRewardEarned(userId: string, rewardName: string) {
await fetch('https://api.stemp.app/api/v1/email/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${installationAccessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: userId,
subject: `You earned a reward: ${rewardName}!`,
htmlBody: `
<h1>Congratulations! 🎉</h1>
<p>You've completed your stamp card and earned: <strong>${rewardName}</strong></p>
<p>Show this email at the counter to redeem your reward.</p>
`,
category: 'transactional',
}),
})
}