Appearance
Overview
Maillune uses two authentication mechanisms:
| Use case | Mechanism |
|---|---|
| Server-to-server API calls (templates, rendering, sessions) | Secret API key |
Browser SDK (<email-builder>) | Short-lived session token |
Never put your secret API key in the browser
Your API key grants full access to your account. Always keep it on your server. Use session tokens for all client-side SDK usage.
API keys
Create and manage keys in the Dashboard.
Keys look like:
mms_live_6839ac7bd38675bc57ca37581b365ea...Pass the key in the Authorization header as a Bearer token for all server-side calls:
bash
curl https://api.maillune.com/v1/templates \
-H "Authorization: Bearer mms_live_YOUR_KEY"Session tokens (SDK)
Before mounting <email-builder> in your frontend, your backend exchanges the secret key for a short-lived session token scoped to a single user.
POST /v1/sessions
Authorization: Bearer <your_secret_api_key>
Content-Type: application/json
{ "userId": "end-user-id-in-your-system" }Response:
json
{
"sessionToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 7200
}Pass sessionToken to the browser and use it as the api-key attribute:
html
<email-builder api-key="eyJ..." user-id="..."></email-builder>Token properties
| Property | Value |
|---|---|
| Lifetime | 2 hours |
| Scope | Single userId, single API key |
| Usable for | SDK validate, image uploads, template saves |
| Revocable | Yes — revoke the parent API key |
Server-side example (Node.js)
js
// Your Express/Next.js route handler
app.get('/api/editor-session', requireAuth, async (req, res) => {
const response = await fetch('https://api.maillune.com/v1/sessions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MAILLUNE_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ userId: req.user.id }),
});
const { sessionToken } = await response.json();
res.json({ sessionToken });
});Origin allowlisting
For an extra layer of security, restrict which domains can use a given API key's session tokens. Requests from unlisted origins are rejected with 403.
Manage allowed origins in the Dashboard → API Keys or via the API:
bash
PATCH /v1/keys/:id/origins
Authorization: Bearer <dashboard_jwt>
{ "origins": ["https://app.example.com", "https://staging.example.com"] }- An empty array means all origins allowed (default — backward compatible)
- Origins are matched against the
Originrequest header
Key validation
Validate a key and retrieve plan details:
bash
GET /v1/keys/validate
Authorization: Bearer mms_live_YOUR_KEYAlso accepts a session token:
bash
GET /v1/keys/validate
Authorization: Bearer eyJ...sessionTokenResponse:
json
{
"plan": "pro",
"limits": {
"renders": 2000,
"storageMb": 500,
"templates": 99999
},
"usage": {
"renders": 412,
"storageMb": 84.3
},
"features": {
"imageUpload": true,
"customFonts": true,
"exportHtml": true,
"templateLibrary": true,
"whiteLabel": true
}
}Base URL
https://api.maillune.comAll endpoints are prefixed with /v1.
Rate limits
| Plan | Requests / minute |
|---|---|
| Free | 60 |
| Pro | 300 |
| Agency | 1,000 |
| Enterprise | Custom |
Exceeded limits return 429 Too Many Requests.
Errors
All errors return a JSON body with an error string:
json
{ "error": "Invalid API key" }| Status | Meaning |
|---|---|
400 | Bad request — missing or invalid parameters |
401 | Unauthorized — missing or invalid key/token |
402 | Plan limit reached — upgrade required |
403 | Origin not in allowlist |
404 | Resource not found |
429 | Rate limit exceeded |
500 | Server error |