API Documentation

Everything you need to send transactional emails via the BejaFly API.

Authentication

All API requests require an API key sent via the X-API-Key header:

curl -H "X-API-Key: bf_live_your_key_here" https://bejafly.com/api/v1/account

You can also use the Authorization: Bearer header:

curl -H "Authorization: Bearer bf_live_your_key_here" https://bejafly.com/api/v1/account

Generate API keys from your dashboard. Keys are shown once on creation.

Send Email

POST /api/v1/send

Using a template

curl -X POST https://bejafly.com/api/v1/send \
  -H "X-API-Key: bf_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "from": {"email": "hello@yourapp.com", "name": "YourApp"},
    "to": {"email": "user@example.com", "name": "Jane"},
    "template": "welcome-email",
    "variables": {
      "name": "Jane",
      "login_url": "https://yourapp.com/login"
    },
    "track_opens": true,
    "track_clicks": true
  }'

Using raw HTML

curl -X POST https://bejafly.com/api/v1/send \
  -H "X-API-Key: bf_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "from": {"email": "hello@yourapp.com", "name": "YourApp"},
    "to": {"email": "user@example.com"},
    "subject": "Your receipt",
    "html": "<h1>Thanks for your purchase</h1><p>Amount: $49</p>"
  }'

Response

{
  "success": true,
  "message_id": "msg_a1b2c3d4e5f6",
  "status": "queued"
}

Parameters

FieldTypeRequiredDescription
from.emailstringYesSender email (domain must be verified)
from.namestringNoSender display name
to.emailstringYesRecipient email
to.namestringNoRecipient display name
subjectstring*Email subject (required if no template)
templatestring*Template slug or ID
variablesobjectNoTemplate variable values
htmlstring*Raw HTML body (required if no template)
textstringNoPlain text fallback
headersobjectNoCustom X-headers
track_opensbooleanNoEnable open tracking (default: true)
track_clicksbooleanNoEnable click tracking (default: true)

Templates

MethodEndpointDescription
GET/api/v1/templatesList all templates
POST/api/v1/templatesCreate template
GET/api/v1/templates/{id}Get template
PUT/api/v1/templates/{id}Update template
DELETE/api/v1/templates/{id}Delete template

Templates use {{variable_name}} placeholders that get replaced with values from the variables object in your send request.

Emails & Events

MethodEndpointDescription
GET/api/v1/emailsList emails (filterable)
GET/api/v1/emails/{message_id}Get email + event timeline

Query Parameters

status — Filter by status (queued, delivered, bounced, failed)

to — Filter by recipient email

page, per_page — Pagination (max 100 per page)

Webhooks

Configure webhook endpoints in your dashboard to receive real-time event notifications.

Event Types

delivered, bounced, opened, clicked, unsubscribed, failed

Payload Example

{
  "event": "delivered",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "msg_a1b2c3d4e5f6",
    "to": "user@example.com",
    "subject": "Welcome to YourApp",
    "template": "welcome-email"
  }
}

Signature Verification

Every webhook includes an X-BejaFly-Signature header:

X-BejaFly-Signature: t=1705312200,v1=5257a869...

# Verify in PHP:
$payload = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_BEJAFLY_SIGNATURE'];
preg_match('/t=(\d+),v1=(.+)/', $header, $m);
$expected = hash_hmac('sha256', $m[1] . '.' . $payload, $your_webhook_secret);
$valid = hash_equals($expected, $m[2]);

Error Codes

CodeHTTPDescription
UNAUTHORIZED401Invalid or missing API key
RATE_LIMITED429Too many requests
VALIDATION_ERROR400Invalid request parameters
DOMAIN_NOT_VERIFIED400Sending domain not verified
QUOTA_EXCEEDED400Monthly email quota exceeded
RECIPIENT_SUPPRESSED400Recipient is on suppression list
TEMPLATE_NOT_FOUND400Template slug/ID not found
NOT_FOUND404Resource not found

Code Examples

PHP

$ch = curl_init('https://bejafly.com/api/v1/send');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'X-API-Key: bf_live_your_key',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'from' => ['email' => 'hello@yourapp.com'],
        'to' => ['email' => 'user@example.com'],
        'template' => 'welcome-email',
        'variables' => ['name' => 'Jane'],
    ]),
    CURLOPT_RETURNTRANSFER => true,
]);
$response = json_decode(curl_exec($ch), true);

Python

import requests

r = requests.post('https://bejafly.com/api/v1/send',
    headers={'X-API-Key': 'bf_live_your_key'},
    json={
        'from': {'email': 'hello@yourapp.com'},
        'to': {'email': 'user@example.com'},
        'template': 'welcome-email',
        'variables': {'name': 'Jane'},
    }
)
print(r.json())

Node.js

const res = await fetch('https://bejafly.com/api/v1/send', {
  method: 'POST',
  headers: {
    'X-API-Key': 'bf_live_your_key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    from: { email: 'hello@yourapp.com' },
    to: { email: 'user@example.com' },
    template: 'welcome-email',
    variables: { name: 'Jane' },
  }),
});
const data = await res.json();

Rate Limits

Rate limits are based on your plan and applied per API key using a token bucket algorithm:

PlanRequests/secMonthly Emails
Free1100/day
Starter52,000
Growth2010,000
Scaling5050,000
Pro100100,000

When rate limited, you'll receive a 429 response with a Retry-After header.