Posts API
Publish newsletter posts from your CMS, blog or automation: create a post, then send it to your subscribers right away or at a time you choose.
A post created through the API is the same as one written in the editor: it appears under Posts in your account, where you can still edit, reschedule or cancel it until sending starts. Posts are written in the name of the newsletter's owner.
Before you start
| Where to find it | |
|---|---|
| API key and API secret | In your obanox account under Account → API. See Authentication. |
| Your newsletter's slug | See Your newsletter's slug. |
| A newsletter ready to send | Only needed to send or schedule, not for drafts: a confirmed email address, an imprint and a sender address in the settings, and a plan that allows sending. |
Create a post
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
API-KEY | Your API key |
API-SECRET | Your API secret |
Body fields
| Field | Type | Required | Description |
|---|---|---|---|
subject | string | yes | Title of the post and subject line of the email. Max. 120 characters, single line. |
html | string | yes | Content of the post as HTML. Scripts, styles and unsafe attributes are removed; links must use http, https or mailto. |
subtitle | string | no | Short teaser shown below the title. Max. 255 characters, single line. |
template_id | integer or null | no | The template the email is sent in. Left out: your newsletter's default template. null: the built-in layout. |
use_template | boolean | no | Default true. false sends html as a complete email in your own design, without any template. Can't be combined with template_id. |
web_visible | boolean | no | Whether the post is published on your newsletter website once sent. Default true. |
send | boolean | no | true sends the post right away. |
send_at | string | no | Send at this time instead. ISO 8601 with time zone, e.g. 2026-11-02T09:00:00+01:00 or 2026-11-02T08:00:00Z. Must be in the future. |
Without send or send_at the post is saved as a draft, to be reviewed and sent in your account. Use either send or send_at, not both.
{
"subject": "Our November update",
"subtitle": "New features and three tips for the holidays",
"html": "<p>Hello,</p><p>here is what is new this month …</p>",
"template_id": 12,
"send_at": "2026-11-02T09:00:00+01:00"
}
Response
201 Created: the post was saved, and scheduled if you asked to send it.
{
"id": 4711,
"status": "scheduled",
"slug": "our-november-update",
"use_template": true,
"template_id": 12,
"send_after": "2026-11-02T09:00:00+01:00"
}
status | Meaning |
|---|---|
draft | Saved, not sent. Send it from your account. |
scheduled | Will be sent at send_after. With send: true that is now, and sending starts within a minute. |
sending | Being sent to your subscribers. |
sent | Sent to all subscribers. |
Examples
curl -X POST "https://obanox.com/api/v1/my-newsletter/posts" \
-H "Content-Type: application/json" \
-H "API-KEY: your-api-key" \
-H "API-SECRET: your-api-secret" \
-d '{
"subject": "Our November update",
"html": "<p>Here is what is new this month …</p>",
"send": true
}'
<?php
$payload = [
'subject' => 'Our November update',
'html' => '<p>Here is what is new this month …</p>',
'send' => true,
];
$ch = curl_init('https://obanox.com/api/v1/my-newsletter/posts');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'API-KEY: ' . getenv('OBANOX_API_KEY'),
'API-SECRET: ' . getenv('OBANOX_API_SECRET'),
],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status !== 201) {
$error = json_decode($body, true)['error'] ?? $body;
// handle the error, see "Errors"
}
const response = await fetch('https://obanox.com/api/v1/my-newsletter/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'API-KEY': process.env.OBANOX_API_KEY,
'API-SECRET': process.env.OBANOX_API_SECRET,
},
body: JSON.stringify({
subject: 'Our November update',
html: '<p>Here is what is new this month …</p>',
send: true,
}),
});
if (response.status !== 201) {
const { error } = await response.json();
// handle the error, see "Errors"
}
import os
import requests
response = requests.post(
"https://obanox.com/api/v1/my-newsletter/posts",
headers={
"API-KEY": os.environ["OBANOX_API_KEY"],
"API-SECRET": os.environ["OBANOX_API_SECRET"],
},
json={
"subject": "Our November update",
"html": "<p>Here is what is new this month …</p>",
"send": True,
},
timeout=10,
)
if response.status_code != 201:
error = response.json().get("error")
# handle the error, see "Errors"
Check a post
Same headers as above, without a body. Returns the post's current state:
{
"id": 4711,
"status": "sent",
"subject": "Our November update",
"slug": "our-november-update",
"use_template": true,
"template_id": 12,
"send_after": "2026-11-02T09:00:00+01:00",
"published_at": "2026-11-02T09:00:12+01:00",
"total_recipients": 1250,
"total_sent": 1250,
"send_error": null,
"url": "https://my-newsletter.obanox.com/our-november-update"
}
send_error: why the post could not be sent yet, e.g. a limit of your plan;nullotherwise. Fix the cause in your account; the post is sent once it can be.url: the post on your newsletter website, once sending started and ifweb_visibleis on;nullotherwise.
Templates
Every post is sent in a template: an HTML design with your header, footer and unsubscribe link. Templates are created in your account under Templates, where you can also choose the default for new posts. Without one of your own, posts use the built-in layout with your logo and accent color.
Lists your newsletter's templates. Use an id as template_id when creating a post.
[
{ "id": 12, "name": "Monthly update", "default": true },
{ "id": 15, "name": "Short announcement", "default": false }
]
Your own design
If your website already builds the complete email, send it with "use_template": false. The html is then sent exactly as you send it: a full HTML document with <head>, <style> and inline styles. Only <script> blocks are removed.
- Required: an unsubscribe link with the placeholder
{{ unsubscribe_url }}, e.g.<a href="{{ unsubscribe_url }}">Unsubscribe</a>. It is replaced with each subscriber's personal link. - Placeholders work as in templates:
{{ first_name }},{{ last_name }},{{ email }},{{ web_url }}(view in browser),{{ newsletter_name }},{{ newsletter_imprint }}and more. - Tracking (if enabled for your newsletter) works as usual: links are tracked and an open pixel is added.
- On your newsletter website the post is shown in the website's design: only the content of
<body>is used, while head, title, styles and anything unsafe are removed. The post subject is the page heading, so headings in your html move down one level (<h1>becomes<h2>;<h6>stays<h6>), and the first<h1>is left out if it repeats the subject. Hidden elements such as a preheader (display:none) are not shown. - Such posts can be previewed, test-sent and cancelled in your account, but not edited in the post editor.
{
"subject": "Our November update",
"use_template": false,
"html": "<!doctype html><html><head><style>h1 { color: #e4572e; }</style></head><body><h1>Hello {{first_name}}</h1><p>…</p><a href=\"{{unsubscribe_url}}\">Unsubscribe</a></body></html>",
"send": true
}
Errors
Errors return a JSON body with an error message. When a post can't be sent, problems lists every reason:
{
"error": "The post cannot be sent.",
"problems": [
"Please enter your imprint in the settings first."
]
}
| Status | error | What it means | What to do |
|---|---|---|---|
401 | Invalid api-key or api-secret. | Headers missing or wrong. | Check your credentials. Send them as API-KEY / API-SECRET (with a hyphen). |
403 | Access to this list denied. | The slug doesn't exist or belongs to another account. | Check the slug. |
404 | Post not found. | No post with this id in this newsletter. | Check the id and slug. |
413 | The request is too large. | The body is over 1 MB. | Link to images instead of embedding them. |
422 | The body must be a JSON object. | The body isn't valid JSON. | Send a JSON object with Content-Type: application/json. |
422 | Missing or invalid fields: … | The listed fields are missing, invalid, too long or contain line breaks. html is also refused when nothing is left of it after cleaning, template_id when it isn't a template of this newsletter. | Fix the listed fields. |
422 | html must contain {{ unsubscribe_url }} when use_template is false. | Your own design has no unsubscribe link. | Add a link to {{ unsubscribe_url }}. |
422 | send_at must be in the future. | The time has passed. | Use send: true to send now. |
422 | The post cannot be sent. | Sending was requested, but the newsletter isn't ready (see problems). Nothing was saved. | Fix the listed problems in your account, then repeat the request, or leave out send/send_at to save a draft. |
5xx | – | Temporary problem on our side. | Retry after a short wait, see Good practice. |
Good practice
- Avoid duplicate posts. Each successful request creates a new post. If a request times out, check under Posts in your account before retrying, or the post may be sent twice.
- Draft first when unsure. Leave out
sendandsend_atto review the post in your account and send a test email before it goes out. - Host your images. Reference images by their
httpsURL in the HTML.
Limits
| Request size | 1 MB |
|---|---|
| Templates | Up to 10 per newsletter; or your own design with "use_template": false |
| Cover image | Not supported over the API; add it in your account |
| Emails sent | According to your plan |
Support
Questions? Contact obanox support and include the post id from the API response.