Sending Emails
Send transactional and bulk emails via the FormaMail API using templates.
Overview
The FormaMail API provides two endpoints for sending emails with API key authentication:
- Single Email: Send one email to one or more recipients using a template
- Bulk Personalized: Send personalized emails to many recipients with individual variables
Important: All emails must use a template. Templates are created via the dashboard.
API Endpoints
Send Single Email
POST /api/emails/sendSend an email using a template to one or more recipients.
Send Bulk Personalized Emails
POST /api/emails/send/bulkSend personalized emails to multiple recipients, each with their own variables.
Authentication
All requests require an API key in the Authorization header:
Authorization: Bearer fm_sk_abc123xyz456...Get your API key from the dashboard: Settings → API Keys
Send Single Email
Request Format
curl -X POST 'http://localhost:3000/api/emails/send' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer fm_sk_abc123...' \
-d '{
"templateId": "tmpl_abc123",
"to": [
{ "email": "user@example.com", "name": "John Doe" }
],
"variables": {
"firstName": "John",
"orderNumber": "12345",
"orderTotal": 99.99
}
}'Request Body
| Field | Type | Required | Description |
|---|---|---|---|
templateId | string | Yes | Template ID from dashboard |
to | array | Yes | Recipients (max 50) |
to[].email | string | Yes | Recipient email address |
to[].name | string | No | Recipient name |
variables | object | No | Template variables |
cc | array | No | CC recipients |
bcc | array | No | BCC recipients |
replyTo | string | No | Reply-to email address |
attachments | array | No | Attachments (see below) |
tags | string[] | No | Tags for categorization |
priority | string | No | high, normal, or low |
scheduledAt | string | No | ISO 8601 datetime for scheduling |
Example: Simple Email
const axios = require('axios');
const API_KEY = 'fm_sk_abc123...';
const API_ENDPOINT = 'http://localhost:3000/api';
async function sendEmail() {
try {
const response = await axios.post(`${API_ENDPOINT}/emails/send`, {
templateId: 'tmpl_welcome',
to: [
{ email: 'user@example.com', name: 'John Doe' }
],
variables: {
firstName: 'John',
companyName: 'Acme Corp'
}
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
}
});
console.log('Email sent successfully:', response.data);
return response.data;
} catch (error) {
console.error('Error sending email:', error.response?.data || error.message);
throw error;
}
}
sendEmail();Example: Email with CC/BCC
{
"templateId": "tmpl_notification",
"to": [
{ "email": "primary@example.com", "name": "Primary User" }
],
"cc": [
{ "email": "manager@example.com", "name": "Manager" }
],
"bcc": [
{ "email": "archive@example.com" }
],
"replyTo": "support@example.com",
"variables": {
"subject": "Project Update",
"message": "The project has been completed."
},
"tags": ["notification", "project"]
}Example: Scheduled Email
{
"templateId": "tmpl_reminder",
"to": [
{ "email": "user@example.com", "name": "John" }
],
"variables": {
"eventName": "Team Meeting",
"eventTime": "2025-11-08 14:00"
},
"scheduledAt": "2025-11-08T09:00:00Z",
"priority": "high"
}Response
Success (200 OK):
{
"id": "email_abc123xyz456",
"status": "queued",
"templateId": "tmpl_welcome",
"to": [
{ "email": "user@example.com", "name": "John Doe" }
],
"queuedAt": "2025-11-07T10:30:00Z"
}Email Statuses:
pending: Email created but not yet queuedqueued: Email queued for sendingprocessing: Email is being processedscheduled: Email scheduled for future deliverysent: Email sent to provider (AWS SES)delivered: Email delivered to recipient’s inboxfailed: Email sending failed permanentlybounced: Email bounced (delivery failed)rejected: Email rejected by recipient server
Send Bulk Personalized Emails
Send personalized emails to multiple recipients, each with their own variables.
Request Format
curl -X POST 'http://localhost:3000/api/emails/send/bulk' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer fm_sk_abc123...' \
-d '{
"templateId": "tmpl_order_confirmation",
"recipients": [
{
"email": "user1@example.com",
"name": "John Doe",
"variables": {
"orderNumber": "12345",
"orderTotal": 99.99
}
},
{
"email": "user2@example.com",
"name": "Jane Smith",
"variables": {
"orderNumber": "12346",
"orderTotal": 149.99
}
}
],
"baseVariables": {
"companyName": "Acme Corp",
"supportEmail": "support@acme.com"
},
"batchName": "Daily Order Confirmations"
}'Request Body
| Field | Type | Required | Description |
|---|---|---|---|
templateId | string | Yes | Template ID from dashboard |
recipients | array | Yes | Array of recipients (max 1000) |
recipients[].email | string | Yes | Recipient email |
recipients[].name | string | No | Recipient name |
recipients[].variables | object | Yes | Per-recipient variables |
baseVariables | object | No | Shared variables for all recipients |
batchName | string | No | Name for this batch |
tags | string[] | No | Tags for all emails |
priority | string | No | Priority for all emails |
Example: JavaScript
const axios = require('axios');
const API_KEY = 'fm_sk_abc123...';
const API_ENDPOINT = 'http://localhost:3000/api';
async function sendBulkEmails() {
try {
const response = await axios.post(`${API_ENDPOINT}/emails/send/bulk`, {
templateId: 'tmpl_order_confirmation',
recipients: [
{
email: 'user1@example.com',
name: 'John Doe',
variables: {
orderNumber: '12345',
orderTotal: 99.99,
items: [
{ name: 'Product A', price: 49.99 },
{ name: 'Product B', price: 49.99 }
]
}
},
{
email: 'user2@example.com',
name: 'Jane Smith',
variables: {
orderNumber: '12346',
orderTotal: 149.99,
items: [
{ name: 'Product C', price: 149.99 }
]
}
}
],
baseVariables: {
companyName: 'Acme Corp',
supportEmail: 'support@acme.com'
},
batchName: 'Daily Order Confirmations',
tags: ['order', 'confirmation']
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
}
});
console.log('Bulk emails queued:', response.data);
return response.data;
} catch (error) {
console.error('Error sending bulk emails:', error.response?.data || error.message);
throw error;
}
}
sendBulkEmails();Example: Python
import requests
API_KEY = 'fm_sk_abc123...'
API_ENDPOINT = 'http://localhost:3000/api'
def send_bulk_emails():
url = f'{API_ENDPOINT}/emails/send/bulk'
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {API_KEY}'
}
payload = {
'templateId': 'tmpl_order_confirmation',
'recipients': [
{
'email': 'user1@example.com',
'name': 'John Doe',
'variables': {
'orderNumber': '12345',
'orderTotal': 99.99
}
},
{
'email': 'user2@example.com',
'name': 'Jane Smith',
'variables': {
'orderNumber': '12346',
'orderTotal': 149.99
}
}
],
'baseVariables': {
'companyName': 'Acme Corp'
},
'batchName': 'Daily Orders'
}
try:
response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()
print('Bulk emails queued:', response.json())
return response.json()
except requests.exceptions.RequestException as error:
print('Error sending bulk emails:', error)
if hasattr(error, 'response') and error.response is not None:
print('Response:', error.response.text)
raise
if __name__ == '__main__':
send_bulk_emails()Response
Success (200 OK):
{
"batchId": "batch_abc123",
"status": "queued",
"totalRecipients": 2,
"queuedAt": "2025-11-07T10:30:00Z"
}Attachments
You can include attachments in emails using two methods:
1. Template-Generated Attachments
Generate PDF or Excel files from attachment templates:
{
"templateId": "tmpl_email",
"to": [{ "email": "user@example.com" }],
"variables": { "name": "John" },
"attachments": [
{
"filename": "invoice.pdf",
"attachmentTemplateId": "tmpl_pdf_invoice",
"variables": {
"invoiceNumber": "INV-2025-001",
"amount": 99.99
},
"outputFormats": ["pdf"]
}
]
}Attachment Fields:
filename: Name of the fileattachmentTemplateId: Template ID for PDF/Excel (created in dashboard)variables: Variables for the attachment templateoutputFormats: Array of formats (["pdf"]or["excel"])
2. Pre-Uploaded Files
Attach files by providing base64-encoded content:
{
"templateId": "tmpl_email",
"to": [{ "email": "user@example.com" }],
"attachments": [
{
"filename": "document.pdf",
"content": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PC...",
"contentType": "application/pdf"
}
]
}Note: For large files, use template-generated attachments instead of base64 encoding.
Template Variables
Templates support variables using Handlebars syntax {{variableName}}.
Example Template:
<h1>Hello {{firstName}}!</h1>
<p>Your order #{{orderNumber}} totals ${{orderTotal}}.</p>Variables:
{
"firstName": "John",
"orderNumber": "12345",
"orderTotal": 99.99
}Result:
<h1>Hello John!</h1>
<p>Your order #12345 totals $99.99.</p>Supported Variable Types
- Strings:
"John Doe" - Numbers:
99.99 - Booleans:
true/false - Arrays:
["item1", "item2"] - Objects:
{ "name": "John", "age": 30 }
Conditionals
{{#if isPremium}}
<p>Thank you for being a premium member!</p>
{{else}}
<p>Upgrade to premium for exclusive benefits.</p>
{{/if}}Loops
<ul>
{{#each items}}
<li>{{name}} - ${{price}}</li>
{{/each}}
</ul>Error Responses
Invalid Template ID
Status Code: 404 Not Found
{
"statusCode": 404,
"code": "ERR_TMPL_002",
"message": "Email template not found",
"timestamp": "2025-11-07T10:30:00Z",
"path": "/api/emails/send",
"relatedInfo": {
"templateId": "tmpl_invalid"
}
}Invalid Email Format
Status Code: 400 Bad Request
{
"statusCode": 400,
"code": "ERR_VALID_001",
"message": "Validation failed",
"relatedInfo": {
"errors": [
{
"field": "to[0].email",
"message": "Invalid email format"
}
]
}
}Missing Required Variable
Status Code: 400 Bad Request
{
"statusCode": 400,
"code": "ERR_TMPL_004",
"message": "Missing required template variable",
"relatedInfo": {
"variable": "firstName",
"templateId": "tmpl_welcome"
}
}Quota Exceeded
Status Code: 429 Too Many Requests
{
"statusCode": 429,
"code": "ERR_QUOTA_001",
"message": "Monthly email quota exceeded",
"relatedInfo": {
"limit": 10000,
"used": 10000,
"resetsAt": "2025-12-01T00:00:00Z"
}
}Sending Emails with Charts
Charts can be included in email templates to visualize data like sales trends, analytics, or performance metrics. In emails, charts are rendered as high-resolution static images via QuickCharts API.
Example: Weekly Sales Report Email
const axios = require('axios');
const API_KEY = 'fm_sk_abc123...';
const API_ENDPOINT = 'http://localhost:3000/api';
async function sendSalesReportEmail() {
try {
const response = await axios.post(`${API_ENDPOINT}/emails/send`, {
templateId: 'tmpl_weekly_sales_report', // Template with chart component
to: [
{ email: 'manager@company.com', name: 'Sales Manager' }
],
variables: {
// Text variables
reportWeek: 'Week 47, 2025',
reportDate: 'November 25, 2025',
// Chart data: array of objects with label and value fields
weeklySales: [
{ day: 'Monday', revenue: 12500 },
{ day: 'Tuesday', revenue: 15200 },
{ day: 'Wednesday', revenue: 13800 },
{ day: 'Thursday', revenue: 16500 },
{ day: 'Friday', revenue: 18200 },
{ day: 'Saturday', revenue: 21000 },
{ day: 'Sunday', revenue: 14500 }
],
// Summary metrics
totalRevenue: 111700,
averageDaily: 15957,
growthPercent: 12.5
}
}, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
}
});
console.log('Sales report sent:', response.data);
return response.data;
} catch (error) {
console.error('Error sending sales report:', error.response?.data || error.message);
throw error;
}
}
sendSalesReportEmail();Chart Data Format
The chart component in your template expects data in a specific format:
{
// For bar/line/area charts (time series or categories)
chartData: [
{ label: 'January', value: 12500 }, // label: x-axis, value: y-axis
{ label: 'February', value: 15200 },
{ label: 'March', value: 13800 }
],
// For pie/doughnut charts (proportions)
categoryData: [
{ label: 'Product A', value: 45 }, // value: percentage or amount
{ label: 'Product B', value: 30 },
{ label: 'Product C', value: 25 }
]
}Field Mapping: The xAxis and yAxis properties in your chart component (configured in the template) specify which fields to use from your data objects. For example:
xAxis: "label"→ uses thelabelfield for X-axisyAxis: "value"→ uses thevaluefield for Y-axis
Template Configuration (Dashboard)
In your template designer, configure the chart component with these properties:
| Property | Value | Description |
|---|---|---|
chartType | "bar" | Bar, line, pie, doughnut, area, or scatter |
dataSource | "{{weeklySales}}" | Variable containing array data |
xAxis | "day" | Field name for X-axis (e.g., “day”, “label”, “month”) |
yAxis | "revenue" | Field name for Y-axis (e.g., “revenue”, “value”, “amount”) |
title | "Weekly Revenue" | Chart title |
width | 600 | Chart width (max 600px for emails) |
height | 400 | Chart height (300-400px recommended) |
Multiple Charts in One Email
{
templateId: 'tmpl_performance_dashboard',
to: [{ email: 'executive@company.com' }],
variables: {
// Chart 1: Revenue trend
monthlyRevenue: [
{ month: 'Jan', revenue: 45000 },
{ month: 'Feb', revenue: 52000 },
{ month: 'Mar', revenue: 48000 }
],
// Chart 2: Category breakdown
revenueByCategory: [
{ category: 'Products', amount: 85000 },
{ category: 'Services', amount: 45000 },
{ category: 'Support', amount: 15000 }
],
// Chart 3: Regional performance
regionalSales: [
{ region: 'North', sales: 62000 },
{ region: 'South', sales: 45000 },
{ region: 'East', sales: 38000 }
]
}
}Chart Best Practices for Emails
- Keep data concise: Limit to 7-10 data points for readability
- Use appropriate chart types:
- Bar charts: Comparing categories
- Line charts: Trends over time
- Pie/doughnut: Proportions (max 5-6 slices)
- Optimize dimensions: Width ≤ 600px to fit email clients
- Use clear labels: Short, descriptive axis labels
- Test in email clients: Preview in Gmail, Outlook, Apple Mail
Code Examples
PHP
<?php
$apiKey = 'fm_sk_abc123...';
$apiEndpoint = 'http://localhost:3000/api';
function sendEmail($apiKey, $apiEndpoint) {
$url = $apiEndpoint . '/emails/send';
$payload = [
'templateId' => 'tmpl_welcome',
'to' => [
['email' => 'user@example.com', 'name' => 'John Doe']
],
'variables' => [
'firstName' => 'John',
'companyName' => 'Acme Corp'
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
echo "cURL Error: " . $error . "\n";
return false;
}
$result = json_decode($response, true);
if ($httpCode >= 200 && $httpCode < 300) {
echo "Email sent successfully:\n";
print_r($result);
return $result;
} else {
echo "Error sending email (HTTP $httpCode):\n";
print_r($result);
return false;
}
}
sendEmail($apiKey, $apiEndpoint);
?>Ruby
require 'net/http'
require 'json'
API_KEY = 'fm_sk_abc123...'
API_ENDPOINT = 'http://localhost:3000/api'
def send_email
uri = URI("#{API_ENDPOINT}/emails/send")
payload = {
templateId: 'tmpl_welcome',
to: [
{ email: 'user@example.com', name: 'John Doe' }
],
variables: {
firstName: 'John',
companyName: 'Acme Corp'
}
}
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.path)
request['Content-Type'] = 'application/json'
request['Authorization'] = "Bearer #{API_KEY}"
request.body = payload.to_json
response = http.request(request)
if response.code.to_i >= 200 && response.code.to_i < 300
puts "Email sent successfully:"
puts JSON.parse(response.body)
else
puts "Error sending email (HTTP #{response.code}):"
puts response.body
end
end
send_emailBest Practices
1. Use Template Variables
Don’t hardcode dynamic content in templates. Use variables:
âś… Good:
{
"templateId": "tmpl_welcome",
"variables": { "firstName": "John" }
}❌ Bad: Creating a new template for each user.
2. Validate Emails
Always validate email addresses before sending:
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}3. Handle Errors Gracefully
try {
const result = await sendEmail(data);
console.log('Success:', result.id);
} catch (error) {
if (error.response?.status === 404) {
console.error('Template not found');
} else if (error.response?.status === 429) {
console.error('Quota exceeded');
} else {
console.error('Failed:', error.message);
}
}4. Use Bulk API for Multiple Recipients
For sending to many recipients, use /emails/send/bulk:
// âś… Good: One API call
await sendBulkEmails({
templateId: 'tmpl_newsletter',
recipients: users.map(user => ({
email: user.email,
name: user.name,
variables: { firstName: user.firstName }
}))
});
// ❌ Bad: Many API calls
for (const user of users) {
await sendEmail({ ... }); // Slow and hits rate limits
}5. Test Before Production
Before going live:
- Use a test email address you control
- Verify template rendering with sample data
- Check email delivery and formatting
Rate Limits
Email sending is rate limited based on authentication type:
| Authentication | Single Email Send | Bulk Email Send |
|---|---|---|
| API Key | 100 requests/min | 10 requests/min |
| JWT (Dashboard) | 100 requests/min | 10 requests/min |
| OAuth | 50 requests/min | 5 requests/min |
Note: Rate limits are per team, not per user. All team members and API keys share the same limit.
See Rate Limiting for details on handling rate limits.
Related: Authentication | Templates (Dashboard) | Error Handling