The MailForge API lets you integrate email marketing into any application. Push contacts from your CRM, trigger campaigns, and receive real-time webhook events. All endpoints return JSON.
/api/v1/. The API is stateless — include your API key on every request.All requests require an API key, generated from Settings → API Keys. Pass it as a Bearer token or via the X-API-Key header.
# Option 1: Authorization header (recommended) curl -H "Authorization: Bearer mf_your_api_key_here" \ https://yourapp.com/api/v1/account # Option 2: X-API-Key header curl -H "X-API-Key: mf_your_api_key_here" \ https://yourapp.com/api/v1/contacts
Replace https://yourapp.com with your MailForge instance URL. All endpoints are relative to:
https://yourapp.com/api/v1/
All responses use the same envelope. Check success and the HTTP status code.
{
"success": false,
"error": "email is required"
}
| Status | Meaning |
|---|---|
| 200 | Success |
| 201 | Resource created |
| 400 | Bad request — missing or invalid parameters |
| 401 | Unauthorized — invalid or missing API key |
| 403 | Forbidden — organization inactive |
| 404 | Resource not found |
| 500 | Server error |
/api/v1/contacts
Returns paginated contacts for your organization.
| Parameter | Type | Description |
|---|---|---|
| pageopt | integer | Page number, default 1 |
| per_pageopt | integer | Results per page, max 100, default 25 |
| statusopt | string | Filter: active, unsubscribed, bounced, complained |
| list_idopt | integer | Filter by contact list ID |
| qopt | string | Search by email address |
curl -H "Authorization: Bearer mf_key" \ "https://yourapp.com/api/v1/contacts?page=1&per_page=25&status=active"
{
"success": true,
"data": [{ "id": 1, "email": "alice@example.com", "first_name": "Alice", "status": "active", ... }],
"meta": { "page": 1, "per_page": 25, "total": 150, "pages": 6 }
}
/api/v1/contacts
Create a new contact, or update an existing one if the email already exists (upsert).
| Field | Type | Description |
|---|---|---|
| emailreq | string | Contact email address |
| first_nameopt | string | First name |
| last_nameopt | string | Last name |
| phoneopt | string | Phone number |
| companyopt | string | Company name |
| cityopt | string | City |
| countryopt | string | Country |
| statusopt | string | active (default), unsubscribed |
| sourceopt | string | Origin: api, crm, import (default: api) |
| list_idsopt | array | List IDs to add this contact to |
curl -X POST https://yourapp.com/api/v1/contacts \ -H "Authorization: Bearer mf_key" \ -H "Content-Type: application/json" \ -d '{ "email": "john@acme.com", "first_name": "John", "last_name": "Doe", "company": "ACME Corp", "source": "crm", "list_ids": [1, 3] }'
/api/v1/contacts/subscribe
Quick subscribe endpoint, ideal for web forms, popups, and landing pages. Handles new contacts and re-subscribes existing ones.
// Call from your website's email signup form async function subscribe(email, name) { const res = await fetch('/api/v1/contacts/subscribe', { method: 'POST', headers: { 'Authorization': 'Bearer mf_your_api_key', 'Content-Type': 'application/json', }, body: JSON.stringify({ email, first_name: name, list_id: 1, // optional: add to specific list source: 'website-footer' }) }); const data = await res.json(); // { success: true, data: { subscribed: true, email: "..." } } }
/api/v1/contacts/{id}Get a single contact by ID.
curl -H "Authorization: Bearer mf_key" https://yourapp.com/api/v1/contacts/42
/api/v1/contacts/{id}Update contact fields. Send only the fields you want to change.
curl -X PUT https://yourapp.com/api/v1/contacts/42 \ -H "Authorization: Bearer mf_key" \ -H "Content-Type: application/json" \ -d '{"company": "New Corp", "status": "active"}'
/api/v1/contacts/{id}Permanently delete a contact and all associated send records from your organization.
curl -X DELETE -H "Authorization: Bearer mf_key" https://marketing.bithost.in/api/v1/contacts/42
{
"success": true,
"data": { "deleted": true, "id": 42 }
}/api/v1/contacts/{id}Permanently delete a contact. This cannot be undone.
curl -X DELETE -H "Authorization: Bearer mf_key" https://yourapp.com/api/v1/contacts/42
/api/v1/contacts/unsubscribeMark a contact as unsubscribed. Fire a contact.unsubscribed webhook event.
curl -X POST https://yourapp.com/api/v1/contacts/unsubscribe \ -H "Authorization: Bearer mf_key" \ -H "Content-Type: application/json" \ -d '{"email": "user@example.com"}'
/api/v1/listsList all contact lists for your organization.
{
"success": true,
"data": [
{ "id": 1, "name": "Newsletter Subscribers", "subscriber_count": 1245 },
{ "id": 2, "name": "Product Updates", "subscriber_count": 820 }
]
}
/api/v1/listsCreate a new contact list.
curl -X POST https://yourapp.com/api/v1/lists \ -H "Authorization: Bearer mf_key" \ -H "Content-Type: application/json" \ -d '{"name": "VIP Customers", "description": "High-value segment"}'
/api/v1/campaignsList campaigns with status and basic stats.
| Parameter | Type | Description |
|---|---|---|
| statusopt | string | Filter: draft, scheduled, sending, sent, paused, cancelled |
/api/v1/campaigns/{id}/statsFull analytics for a campaign.
{
"success": true,
"data": {
"campaign_id": 5,
"name": "April Newsletter",
"stats": {
"total_recipients": 1200, "sent": 1198, "delivered": 1185,
"delivery_rate": 98.9, "unique_opens": 423, "open_rate": 35.7,
"unique_clicks": 89, "click_rate": 7.5, "ctor": 21.0,
"bounced": 13, "bounce_rate": 1.1,
"unsubscribed": 4, "unsubscribe_rate": 0.3,
"spam_complaints": 0, "complaint_rate": 0.0
}
}
}
/api/v1/accountOrganization account info including current plan and email usage.
Configure webhooks in Settings → Integrations. MailForge will POST to your endpoint when these events occur.
{
"event": "contact.subscribed",
"timestamp": "1712345678",
"data": {
"id": 123,
"email": "user@example.com",
"first_name": "Jane",
"company": "Acme Corp",
"status": "active",
"source": "website-footer",
"subscribed_at": "2025-04-06T10:30:00"
}
}
Each webhook includes an X-Mailforge-Signature header (HMAC-SHA256 of the raw body using your webhook secret). Verify it to ensure the request is authentic.
const crypto = require('crypto'); function verifyWebhook(req, webhookSecret) { const signature = req.headers['x-mailforge-signature']; const timestamp = req.headers['x-mailforge-timestamp']; const expected = crypto .createHmac('sha256', webhookSecret) .update(`${timestamp}.${JSON.stringify(req.body)}`) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); }
/api/v1/contacts/bulkBulk upsert up to 1,000 contacts in a single call. Existing contacts are updated; new ones are created. Suppressed emails are silently skipped.
| Field | Type | Description |
|---|---|---|
| contactsreq | array | Array of contact objects (max 1,000) |
| list_idopt | integer | Add all contacts to this list |
curl -X POST https://yourapp.com/api/v1/contacts/bulk \ -H "Authorization: Bearer mf_key" \ -H "Content-Type: application/json" \ -d '{ "list_id": 1, "contacts": [ {"email": "alice@company.com", "first_name": "Alice", "company": "Acme"}, {"email": "bob@company.com", "first_name": "Bob", "source": "crm"} ] }'
{ "success": true, "data": { "created": 1, "updated": 1, "skipped": 0, "total": 2 } }
/api/v1/contacts/{id}/tagsReplace the full tag set for a contact.
curl -X PUT https://yourapp.com/api/v1/contacts/42/tags \ -H "Authorization: Bearer mf_key" \ -d '{"tags": ["vip", "enterprise", "demo-attended"]}'
/api/v1/templatesList all active email templates in your organization.
/api/v1/suppressionsList all suppressed email addresses (unsubscribes, bounces, complaints, and manually suppressed).
[{ "email": "user@old.com", "reason": "unsubscribed", "added_at": "2025-03-01T..." }]
/api/v1/suppressionsAdd an email address to the global suppression list. Suppressed addresses are never sent to, even if re-added to a contact list.
curl -X POST https://yourapp.com/api/v1/suppressions \
-d '{"email": "optout@example.com", "reason": "manual"}'
/api/v1/suppressions/{email}Remove an email from the suppression list (re-enables sending to this address).
/api/v1/account/usageReal-time usage and quota information for your organization.
{
"plan": "growth", "subscription_status": "active",
"usage": {
"emails_sent": 4820, "emails_limit": 25000, "emails_pct": 19.3,
"ai_used": 312, "ai_limit": 8000, "ai_remaining": 7688
}
}
Each inbound handler has a unique address (token@inbound.marketing.bithost.in).
Configure your DNS MX record or email routing rule to POST parsed emails to the receive endpoint.
MailForge automatically creates contacts, fires webhooks, and logs every received email.
/api/v1/inboundList all inbound email handlers for your organization.
[{
"id": 1, "name": "Lead Capture", "is_active": true,
"inbound_address": "abc123xyz@inbound.marketing.bithost.in",
"emails_received": 142, "forward_url": "https://yourcrm.com/hooks/email",
"auto_create_contact": true, "auto_tag": "inbound,lead"
}]
When an email arrives at your inbound address and you have a forward URL configured, MailForge POSTs this signed JSON to your endpoint.
{
"event": "email.received",
"timestamp": "2025-04-15T10:30:00Z",
"inbound_address": "abc123@inbound.marketing.bithost.in",
"from_email": "customer@company.com",
"from_name": "Jane Doe",
"subject": "Interested in your Growth plan",
"body_text": "Hi, I'd like more information...",
"attachments": 0,
"contact": {
"id": 1842,
"email": "customer@company.com",
"created": true
}
}
const crypto = require('crypto'); function verifyInbound(req, forwardSecret) { const sig = req.headers['x-mailforge-signature']; const expected = crypto .createHmac('sha256', forwardSecret) .update(req.rawBody) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex') ); }
/api/v1/campaigns/{id}/sendTrigger a draft or scheduled campaign to send immediately. Plan limits are checked before sending.
/api/v1/account/usage before triggering.
curl -X POST https://yourapp.com/api/v1/campaigns/5/send \
-H "Authorization: Bearer mf_key"
{ "success": true, "data": { "campaign_id": 5, "status": "sending" } }
/api/v1/inboundList all inbound email handlers for your organization.
{
"success": true,
"data": [{
"id": 1,
"name": "Sales Inbox",
"inbound_address": "abc123@inbound.marketing.bithost.in",
"is_active": true,
"emails_received": 42,
"auto_create_contact": true,
"auto_tag": "inbound,lead"
}]
}/api/v1/inboundCreate a new inbound email handler. Returns the unique inbound email address and signing secret.
| Field | Type | Description |
|---|---|---|
| nameopt | string | Label for this handler (default: "Inbound Handler") |
| forward_urlopt | string | HTTPS URL to POST parsed email payload to |
| auto_create_contactopt | bool | Auto-create contact from sender (default: true) |
| auto_tagopt | string | Comma-separated tags to apply to created contact |
| list_idopt | integer | Add created contact to this list |
curl -X POST https://yourapp.com/api/v1/inbound \ -H "Authorization: Bearer mf_key" \ -H "Content-Type: application/json" \ -d '{ "name": "Sales Inbox", "forward_url": "https://yourcrm.com/webhooks/email", "auto_create_contact": true, "auto_tag": "inbound,hot-lead", "list_id": 1 }'
{
"success": true,
"data": {
"id": 1,
"inbound_address": "abc123@inbound.marketing.bithost.in",
"secret": "wh_sec_xxxxxxxx"
}
}This is the endpoint your MX handler or SMTP relay POSTs parsed emails to. It's a public endpoint (no API key needed) — secured by the inbound address token in the URL.
/inbound/receive/{token}Receive a parsed inbound email. Configure your mail server to POST here.
| Field | Type | Description |
|---|---|---|
| from_emailreq | string | Sender email address |
| from_nameopt | string | Sender display name |
| subjectopt | string | Email subject line |
| body_textopt | string | Plain text body |
| body_htmlopt | string | HTML body |
| headersopt | object | Raw email headers |
| attachmentsopt | array | Attachment metadata |
curl -X POST https://yourapp.com/inbound/receive/abc123token \ -H "Content-Type: application/json" \ -H "X-Mailforge-Signature: sha256=..." \ -d '{ "from_email": "lead@acmecorp.com", "from_name": "Priya Sharma", "subject": "Interested in your Pro plan", "body_text": "Hi, I would like to learn more...", "attachments": [] }'
After processing, MailForge POSTs this signed payload to your forward_url.
{
"event": "email.received",
"timestamp": "2025-04-15T09:22:14Z",
"inbound_address":"abc123@inbound.marketing.bithost.in",
"from_email": "lead@acmecorp.com",
"from_name": "Priya Sharma",
"subject": "Interested in your Pro plan",
"body_text": "Hi, I would like to learn more...",
"attachments": 0,
"contact": {
"id": 1842,
"email": "lead@acmecorp.com",
"created": true,
"tags": "inbound,hot-lead",
"list_added": "Newsletter Subscribers"
}
}
// Headers included on every inbound forward:
// X-Mailforge-Signature: hmac-sha256-of-body
// X-Mailforge-Event: email.received
// X-Mailforge-Timestamp: unix-timestampX-Mailforge-Signature header on every inbound forward using HMAC-SHA256 with the secret returned when you created the handler./api/v1/webhooksList all outbound webhook subscriptions for your organization.
/api/v1/webhooksSubscribe to outbound events. Returns the signing secret to verify incoming payloads.
| Field | Type | Description |
|---|---|---|
| urlreq | string | HTTPS endpoint to deliver events to |
| eventsreq | array | List of event names to subscribe to |
| nameopt | string | Display name for this webhook |
curl -X POST https://yourapp.com/api/v1/webhooks \ -H "Authorization: Bearer mf_key" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourcrm.com/webhooks/mailforge", "name": "CRM Sync", "events": [ "contact.subscribed", "contact.unsubscribed", "email.opened", "email.clicked" ] }'
/api/v1/campaigns/{id}Get full details of a single campaign.
curl -H "Authorization: Bearer mf_key" https://yourapp.com/api/v1/campaigns/5/api/v1/campaigns/{id}/sendsPaginated list of individual send records for a campaign. Filterable by status.
| Parameter | Type | Description |
|---|---|---|
| statusopt | string | sent | bounced | failed | pending |
/api/v1/campaigns/{id}/sendTrigger an immediate send for a draft, paused, or scheduled campaign. Performs quota check before queuing.
curl -X POST -H "Authorization: Bearer mf_key" https://yourapp.com/api/v1/campaigns/5/send{
"success": true,
"data": {
"queued": true,
"campaign_id": 5,
"message": "Campaign queued for sending."
}
}/api/v1/lists/{id}Get a list with its paginated active contacts.
curl -H "Authorization: Bearer mf_key" "https://yourapp.com/api/v1/lists/1?page=1&per_page=25"/api/v1/lists/{id}/contactsAdd an existing contact (by email) to a list.
curl -X POST https://yourapp.com/api/v1/lists/1/contacts \ -H "Authorization: Bearer mf_key" \ -H "Content-Type: application/json" \ -d '{"email": "user@example.com"}'
/api/v1/account/usageCurrent month usage plus last 12 months send history for charting.
{
"success": true,
"data": {
"current_month": { "sent": 18, "limit": 25000, "pct": 0.1 },
"history": [
{ "year": 2025, "month": 3, "sent": 12450 },
{ "year": 2025, "month": 4, "sent": 18900 }
]
}
}Pure fetch — works in browsers, Node.js 18+, Deno, and Bun. No dependencies required.
class MailForge { constructor(apiKey, baseUrl = 'https://yourapp.com') { this.baseUrl = baseUrl; this.headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }; } async subscribe(data) { const r = await fetch(`${this.baseUrl}/api/v1/contacts/subscribe`, { method: 'POST', headers: this.headers, body: JSON.stringify(data) }); return r.json(); } async getContacts(params = {}) { const q = new URLSearchParams(params); const r = await fetch(`${this.baseUrl}/api/v1/contacts?${q}`, { headers: this.headers }); return r.json(); } async getCampaignStats(campaignId) { const r = await fetch(`${this.baseUrl}/api/v1/campaigns/${campaignId}/stats`, { headers: this.headers }); return r.json(); } } // Usage const mf = new MailForge('mf_your_api_key'); await mf.subscribe({ email: 'user@example.com', list_id: 1 });
Use PHP's built-in cURL or Guzzle to integrate with the MailForge REST API. No dedicated SDK required — the API is plain JSON over HTTPS.
<?php // Install Guzzle: composer require guzzlehttp/guzzle // Or use plain cURL (shown below) $apiKey = 'mf_your_api_key_here'; $baseUrl = 'https://marketing.bithost.in/api/v1'; // ── Subscribe a contact ────────────────────────── $ch = curl_init($baseUrl . '/contacts/subscribe'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $apiKey, 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'email' => 'user@example.com', 'first_name' => 'Priya', 'last_name' => 'Sharma', 'list_id' => 1, 'tags' => 'website,lead', ]), ]); $response = curl_exec($ch); $data = json_decode($response, true); curl_close($ch); if ($data['success']) { echo 'Subscribed: ' . $data['data']['email']; }
$page = 1; $perPage = 25; $url = $baseUrl . "/contacts?page={$page}&per_page={$perPage}&status=active"; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $apiKey, ], ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); foreach ($data['data'] as $contact) { echo $contact['email'] . PHP_EOL; } // Pagination info in $data['meta'] // total, pages, has_next, has_prev
$campaignId = 42; $ch = curl_init($baseUrl . "/campaigns/{$campaignId}/send"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $apiKey, ], CURLOPT_POSTFIELDS => '', ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); echo $data['data']['message']; // "Campaign queued for sending."
// In your webhook endpoint (receives inbound email events) $secret = 'your_forward_secret_from_handler'; $payload = file_get_contents('php://input'); $sig = $_SERVER['HTTP_X_MAILFORGE_SIGNATURE'] ?? ''; $expected = hash_hmac('sha256', $payload, $secret); if (!hash_equals($expected, $sig)) { http_response_code(401); die('Invalid signature'); } $event = json_decode($payload, true); $email = $event['from_email']; $subj = $event['subject']; // Process the inbound email event... echo json_encode(['status' => 'ok']);
$contacts = [ ['email' => 'alice@example.com', 'first_name' => 'Alice', 'company' => 'ACME'], ['email' => 'bob@example.com', 'first_name' => 'Bob'], // ... up to 500 per request ]; $ch = curl_init($baseUrl . '/contacts/bulk'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $apiKey, 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'contacts' => $contacts, 'list_id' => 1, // optional ]), ]); $result = json_decode(curl_exec($ch), true)['data']; curl_close($ch); echo "Created: {$result['created']}, Updated: {$result['updated']}, Failed: {$result['failed']}";
import requests class MailForge: def __init__(self, api_key, base_url="https://yourapp.com"): self.base_url = base_url self.headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} def subscribe(self, email, **kwargs): return requests.post(f"{self.base_url}/api/v1/contacts/subscribe", json={"email": email, **kwargs}, headers=self.headers).json() def get_contacts(self, **params): return requests.get(f"{self.base_url}/api/v1/contacts", params=params, headers=self.headers).json() def campaign_stats(self, campaign_id): return requests.get(f"{self.base_url}/api/v1/campaigns/{campaign_id}/stats", headers=self.headers).json() # Usage mf = MailForge("mf_your_api_key") mf.subscribe("user@example.com", first_name="Jane", list_id=1)