REST API (White-Label Email)

Use the Smailor API to integrate white-label email into your product:

  • Let your members create mailboxes on your hosted domain(s)
  • Send outbound email from verified domains
  • Protect domain and IP reputation with automatic safety checks

Messages count toward your account plan limits.

Base URL

Use your app origin:

  • https://smailor.com (production)
  • http://localhost:5173 (local development)

Authentication

Send your API key on every request:

  • Authorization: Bearer <your_key>
  • or X-API-Key: <your_key>

The full key is shown only once in Settings -> API.

  1. Add and verify a custom domain (DNS + DKIM/SPF/DMARC)
  2. Call GET /api/v1/domains to confirm domain status and reputation
  3. Create member mailboxes with POST /api/v1/mailboxes
  4. Send emails with POST /api/v1/send
  5. Continuously monitor bounce/complaint rates per domain

Endpoint: List Domains + Reputation

GET /api/v1/domains

Returns all custom domains in your account with:

  • Verification state (verified)
  • 30-day sends, bounces, complaints
  • Reputation status (healthy, watch, blocked)
  • Recommendation message

Example

curl "https://smailor.com/api/v1/domains" \
  -H "Authorization: Bearer sk_smailor_..."

Endpoint: Create Mailbox (White-Label)

POST /api/v1/mailboxes

Content-Type: application/json

JSON body

Field Type Required Notes
localPart string yes Mailbox local part, example john-doe
domain string yes Must be a verified custom domain in your account
displayName string no Sender display name

Example

curl -X POST "https://smailor.com/api/v1/mailboxes" \
  -H "Authorization: Bearer sk_smailor_..." \
  -H "Content-Type: application/json" \
  -d '{
    "localPart": "john-doe",
    "domain": "mailer.yourhost.com",
    "displayName": "John Doe"
  }'

Reputation Guard

Mailbox creation is automatically blocked when domain reputation is too risky:

  • blocked status => API returns 409 domain_reputation_blocked
  • Prevents large-scale onboarding on unhealthy domains
  • Helps protect IP/domain reputation for all tenants

Endpoint: Send Email

POST /api/v1/send

Content-Type: application/json

JSON body

Field Type Required Notes
to string yes Recipient email
from string yes Must be an active address on a verified custom domain
subject string yes Subject line
text string one of text/html Plain text body
html string one of text/html HTML body (sanitized server-side)

At least one of text or html must be non-empty.

Example

curl -X POST "https://smailor.com/api/v1/send" \
  -H "Authorization: Bearer sk_smailor_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "[email protected]",
    "from": "[email protected]",
    "subject": "Welcome",
    "text": "Your mailbox is now active."
  }'

Endpoint: Contact Widget Submit

POST /api/widget/submit

Content-Type: application/json

Powers the embeddable contact forms generated in Settings -> Widgets. A website visitor fills in a form, and the message lands in your Smailor inbox as a new ticket from that visitor.

Unlike /api/v1/send, this endpoint delivers inbound to your own inbox, so to only has to be an active address on your account — a verified custom domain is not required.

JSON body

Field Type Required Notes
to string yes Must be one of your active Smailor addresses
from string yes Visitor's email; appears as the sender in your inbox
name string no Visitor display name, max 255 chars
subject string yes 1–998 chars
message string yes Plain text, 1–100000 chars. The HTML body is generated and sanitized server-side

Example

curl -X POST "https://smailor.com/api/widget/submit" \
  -H "Authorization: Bearer sk_smailor_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "[email protected]",
    "from": "[email protected]",
    "name": "Jane Doe",
    "subject": "Question about pricing",
    "message": "Hi, do you offer annual billing?"
  }'

Success response

{ "ok": true, "messageId": "" }

Errors

Status Error Meaning
400 invalid_body Schema validation failed (details holds the field errors)
400 invalid_to The to address is not an active address on your account
401 unauthorized Missing or invalid API key
503 service_unavailable Message queue temporarily unavailable — retry
500 internal_error Temporary server issue

CORS

The endpoint answers OPTIONS preflight and allows cross-origin POST, so it can be called from your website's front-end. Restrict the allowed origins with the API_CORS_ORIGIN or API_CORS_ALLOWLIST environment variables (default: *).

Keep your API key server-side

Your API key grants full access to your account, not just widget submissions. Pasting it into front-end JavaScript makes it readable by any visitor via page source or the network tab, and reusable to send mail in your name.

For production, put the key behind your own server and have the browser call your endpoint instead:

// your-server/contact  →  proxies to Smailor with the key from an env var
const upstream = await fetch('https://smailor.com/api/widget/submit', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.SMAILOR_API_KEY}`,
  },
  body: JSON.stringify({ to: RECIPIENT, from: visitorEmail, subject, message }),
});

Set the recipient (to) on your server too — never from the request body — otherwise the endpoint lets anyone send mail to any of your addresses.

Common Errors

Applies to /api/v1 endpoints.

Status Error Meaning
400 invalid_body, domain_not_verified Validation or domain verification issue
401 unauthorized Missing or invalid API key
403 forbidden, card_not_verified Plan/billing restrictions
409 address_taken, domain_reputation_blocked Conflict or safety guard triggered
429 too_many_requests Send frequency limit hit
5xx internal_error Temporary server issue

Best Practices for Reputation Safety

  • Use double opt-in for new senders
  • Warm up new domains gradually
  • Stop onboarding when bounce/complaint rates increase
  • Separate transactional and marketing traffic per domain
  • Monitor domain-level events daily

Reply Templates (Web Session API)

These routes power the in-app template editor and ticket composer. They authenticate with the same session as the dashboard (browser cookies after login), not with Authorization: Bearer sk_… keys used for /api/v1.

Use cases: building automation around your own logged-in browser session, internal tools same-origin with Smailor, or understanding what the UI calls.

Method Path Purpose
GET /api/templates List your templates. Optional query: ?groupId= (UUID) or ?ticketId= (UUID) to scope suggestions like the ticket reply UI.
POST /api/templates Create a template (JSON body below). Max 50 templates per account.
GET /api/templates/{id} Load one template (owner only).
PATCH /api/templates/{id} Update fields (name, description, subject, body_html, body_text, is_default, group_ids).
DELETE /api/templates/{id} Delete template.
POST /api/templates/{id}/preview Body: { "ticketId": "<uuid>" } — returns rendered subject, html, text for that ticket.
POST /api/templates/{id}/use Fire-and-forget use counter (called when a template is inserted in the UI).

Create template (POST /api/templates)

Content-Type: application/json

Field Type Required Notes
name string yes Display name
description string no Short note
subject string yes Can include {{placeholders}}
body_html string yes HTML body, min length 10
body_text string no Plain text; if omitted, derived from HTML
is_default boolean no Mark as default
group_ids string[] (UUID) no Restrict template to groups; omit or empty = all groups

Placeholders

Subject and HTML may use {{variable}} tokens. Common keys: customer_name, customer_email, ticket_id, subject, group_name, sender_name, company_name, date. Unknown keys are left unchanged at render time.

curl -X POST "https://smailor.com/api/templates" \
  -H "Content-Type: application/json" \
  -H "Cookie: " \
  -d '{
    "name": "Thanks",
    "subject": "Re: {{subject}}",
    "body_html": "

Hi {{customer_name}},

Thanks for your message.

" }'

Errors mirror other JSON APIs: 401 when not signed in, 400 for validation or template quota, 403/404 when listing with ticketId you cannot access.

See Also