Mailer API

Documentation for integrating HTML Refresher with the mailing system.

Overview

HTML Refresher turns a saved campaign into a fresh, unique prompt every time it's requested. There are two ways to build a campaign in the UI:

Either way, the mailer's job is the same: save a campaign once through the HTML Refresher UI, then pull its prompt from this API on every send, run it against an LLM, and deliver the returned HTML.

Every response includes the LLM provider, model, and temperature that produced the HTML Refresher preview. Running the prompt through those exact settings gets the closest possible match to what was reviewed and approved.

Base URL: https://htmlrefresher.vibelogicllc.com

Authentication

Every protected endpoint requires an X-API-Key header. Requests missing it receive 401 Unauthorized.

hr_8ee3224ca90950d120a57c09d63be0c3
X-API-Key: hr_8ee3224ca90950d120a57c09d63be0c3

Generate variation from a saved campaign

GET /api/library/:id/generate

The simplest call in the API. Pass a saved campaign ID and get back a freshly worded prompt every time — no parameters needed. This works for both campaign types:

This is the recommended integration for the mailer. Save a campaign once in the HTML Refresher UI, then just call this endpoint with the campaign ID on every send.

Request

GET /api/library/1774275391429-h3oze5/generate
X-API-Key: hr_8ee3224ca90950d120a57c09d63be0c3

No request body needed. The ID comes from GET /api/library — use the id field on any saved item.

Response

{
  "id": "1774275391429-h3oze5",
  "title": "Lowe's · EGO Blower Survey Refresh",
  "prompt": "=== REFRESH CREATIVE REQUEST ===\nBrand: Lowe's\n...",
  "llm": {
    "provider": "openai",
    "model": "gpt-5.4",
    "temperature": 1.5,
    "maxTokens": 4000
  }
}

JavaScript example

const res  = await fetch(
  "https://htmlrefresher.vibelogicllc.com/api/library/1774275391429-h3oze5/generate",
  { headers: { "X-API-Key": "hr_8ee3224ca90950d120a57c09d63be0c3" } }
);
const data = await res.json();

// data.prompt  → send to LLM as user message
// data.llm     → use model + temperature for the LLM call

curl example

curl https://htmlrefresher.vibelogicllc.com/api/library/1774275391429-h3oze5/generate \
  -H "X-API-Key: hr_8ee3224ca90950d120a57c09d63be0c3"

PHP example

$data = json_decode(file_get_contents(
  "https://htmlrefresher.vibelogicllc.com/api/library/1774275391429-h3oze5/generate",
  false,
  stream_context_create(["http" => [
    "header" => "X-API-Key: hr_8ee3224ca90950d120a57c09d63be0c3"
  ]])
), true);

$prompt = $data["prompt"];       // send to LLM
$model  = $data["llm"]["model"]; // "gpt-5.4"
$temp   = $data["llm"]["temperature"]; // 1.5

Library pull — all items

GET /api/library

Returns all prompts saved through the HTML Refresher UI — curated, named, and approved.

Request

GET /api/library
X-API-Key: hr_8ee3224ca90950d120a57c09d63be0c3

Response

An array of library items. Each item has this shape:

{
  "id": "1743462240123-ab8c4f",
  "title": "Costco Meat Box · June send",
  "createdAt": "3/20/2026, 2:14:00 PM",
  "prompt": "=== Core brief ===\nCreate a fresh HTML email...",
  "llm": {
    "provider": "openai",
    "model": "gpt-5.4",
    "temperature": 1.5,
    "maxTokens": 4000
  },
  "state": { }
}
The state field contains the HTML Refresher form state — the mailer can ignore it. Only prompt and llm are needed. If llm is missing (items saved before the API was added), fall back to GET /api/settings/llm.

Library pull — single item

GET /api/library/:id

Fetches one saved prompt by its ID. Useful when the mailer stores a specific item ID to pull on send.

GET /api/library/1743462240123-ab8c4f
X-API-Key: hr_8ee3224ca90950d120a57c09d63be0c3

Returns a single item object in the same shape as above, or 404 if the ID does not exist.

Default LLM settings

GET /api/settings/llm

Returns the server's currently configured default LLM settings. No authentication required. Use this as a fallback when a library item is missing its llm field.

GET /api/settings/llm

Response

{
  "openai": {
    "model": "gpt-5.4",
    "temperature": 1.5,
    "maxTokens": 4000
  },
  "deepseek": {
    "model": "deepseek-chat",
    "temperature": 1.5,
    "maxTokens": 4000
  }
}

Running the prompt through the LLM

The prompt is a plain text string. Send it as a single user message — no system prompt is needed. All instructions are embedded inside the prompt itself.

OpenAI example

POST https://api.openai.com/v1/chat/completions
Authorization: Bearer YOUR_OPENAI_KEY
Content-Type: application/json

{
  "model": "gpt-5.4",
  "temperature": 1.5,
  "max_completion_tokens": 4000,
  "messages": [
    { "role": "user", "content": "<full prompt string from API>" }
  ]
}

DeepSeek example

POST https://api.deepseek.com/chat/completions
Authorization: Bearer YOUR_DEEPSEEK_KEY
Content-Type: application/json

{
  "model": "deepseek-chat",
  "temperature": 1.5,
  "max_tokens": 4000,
  "messages": [
    { "role": "user", "content": "<full prompt string from API>" }
  ]
}
Note: OpenAI uses max_completion_tokens; DeepSeek uses max_tokens.

Every prompt instructs the LLM to use [LINK] as the sole href in the email. After the LLM returns HTML, do a simple string replace before delivering:

const html = llmResponse.replace(/\[LINK\]/g, "https://your-send-url.com/campaign123");

If you pass a custom linkToken in the request (e.g. "{{SEND_URL}}"), replace that token instead.

Getting a fresh variant every call

GET /api/library/:id/generate re-runs the refresh prompt builder against the saved reference creative on every call — there is no seed to manage. Just call the endpoint again on each send and a newly worded rewrite prompt comes back every time.

Apples-to-apples matching: The HTML Refresher preview and the mailer will produce the closest possible results when using the same model and temperature from the llm block. LLMs are non-deterministic even at the same temperature, so output won't be pixel-identical — but the brand, layout, and copy tone stay consistent because those are locked in by the prompt text and the saved reference creative.

Error responses

All errors return JSON:

{ "error": "Human-readable error message." }
StatusMeaning
400Bad request — missing or malformed body
401Missing or invalid X-API-Key
404Library item not found
405Wrong HTTP method for this endpoint
500Server error

Quick reference

EndpointMethodAuthPurpose
/api/library/:id/generateGETYesRecommended. Fresh prompt from a saved campaign (Generate New or Refresh Existing) — just the ID, no params
/api/libraryGETNoList all saved campaigns and their IDs
/api/library/:idGETNoFetch one saved campaign by ID
/api/settings/llmGETNoDefault LLM config (fallback)

HTML Refresher — htmlrefresher.vibelogicllc.com