stemp Logostemp Developer
Platform API

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

FieldRequiredDescription
toYesRecipient ID — a user (usr_xxx) or member (mem_xxx) TypeId
subjectYesEmail subject line
htmlBodyYesHTML content of the email
textBodyNoPlain text version (auto-generated from HTML if omitted)
categoryYesEmail category (see Categories below)
attachmentsNoArray 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 API
  • mem_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:

CategoryValueUnsubscribableDescription
TransactionaltransactionalNoOrder confirmations, receipts, password resets
SecuritysecurityNoSecurity alerts, login notifications
MarketingmarketingYesPromotions, offers, campaigns
Product Updatesproduct_updatesYesFeature announcements, changelog
GeneralgeneralYesGeneral communications

Unsubscribable Categories

For categories marked as unsubscribable (marketing, product_updates, general):

  • stemp automatically adds List-Unsubscribe headers (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..."
    }
  ]
}
FieldRequiredDescription
cidYesContent-ID referenced in HTML via cid:
filenameYesAttachment filename
contentTypeYesMIME type (e.g., image/png, image/jpeg)
base64ContentYesBase64-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 }
}
FieldDescription
statusSENT or FAILED
messageIdDelivery tracking ID (null if failed)
errorMessageError details (null if successful)
categoryThe 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

StatusCodeDescription
400platform.email.user_not_foundUser TypeId not found
400platform.email.member_not_foundMember TypeId not found
400platform.email.invalid_recipient_formatInvalid TypeId format
422Recipient 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',
    }),
  })
}