Maileditor Docs
PluginIntegration

Backend API

Read your users' templates and rendered HTML from your server with the Plugin ID and Secret.

The editor never talks to your page. To find out what a user has built, your server calls the Backend API. It lists the users who have saved work, their templates, and the rendered HTML of any template.

Authentication

Send the Plugin ID and Secret as headers on every request:

X-Plugin-Id: 9f3c1e7a-4b21-4c8e-9a77-1d2f5b6c8e10
X-Plugin-Secret: your_plugin_secret
  • Requests must come from a server. The API has no CORS headers, and any request that carries the secret together with a browser Origin header is refused.
  • Credentials in the query string are refused with credentials_in_query.

Endpoints

MethodPathReturns
GET/plugin/v1/backend/usersUsers who have saved at least one template, with counts and activity dates.
GET/plugin/v1/backend/templatesTemplates, filterable by external_user_id, search, updated_after and updated_before.
GET/plugin/v1/backend/templates/{id}One template's metadata.
GET/plugin/v1/backend/templates/{id}/htmlThe template's rendered HTML.

Paths are relative to https://api.maileditor.net/api. Lists are paginated with per_page (default 25, maximum 100) and page. Full request and response shapes: Backend API reference.

Examples

curl "https://api.maileditor.net/api/plugin/v1/backend/templates?external_user_id=customer-user-4471&per_page=100" \
  -H "Accept: application/json" \
  -H "X-Plugin-Id: $MAILEDITOR_PLUGIN_ID" \
  -H "X-Plugin-Secret: $MAILEDITOR_PLUGIN_SECRET"

A Next.js route that proxies the templates list to your own front end. The secret stays on the server.

app/api/templates/route.ts
export async function GET(request: Request) {
  const user = new URL(request.url).searchParams.get('user')
  if (!user) return Response.json({ error: 'user is required' }, { status: 400 })

  const url = new URL(`${process.env.MAILEDITOR_API_BASE_URL}/plugin/v1/backend/templates`)
  url.searchParams.set('external_user_id', user)
  url.searchParams.set('per_page', '100')

  const upstream = await fetch(url, {
    headers: {
      Accept: 'application/json',
      'X-Plugin-Id': process.env.MAILEDITOR_PLUGIN_ID ?? '',
      'X-Plugin-Secret': process.env.MAILEDITOR_PLUGIN_SECRET ?? '',
    },
    cache: 'no-store',
  })

  if (!upstream.ok) return Response.json({ error: 'upstream error' }, { status: 502 })

  const body = await upstream.json()
  return Response.json(body.result.templates.data)
}

A template in the list looks like this:

{
  "id": 4821,
  "name": "September newsletter",
  "slug": "september-newsletter",
  "thumbnail": "https://…/thumbnails/4821.png",
  "external_user_id": "customer-user-4471",
  "created_at": "2026-08-20T09:14:02Z",
  "updated_at": "2026-08-28T16:40:11Z"
}

Getting the HTML

curl "https://api.maileditor.net/api/plugin/v1/backend/templates/4821/html" \
  -H "X-Plugin-Id: $MAILEDITOR_PLUGIN_ID" \
  -H "X-Plugin-Secret: $MAILEDITOR_PLUGIN_SECRET"

The HTML is a string in result.html. source is media when the last save has been rendered and draft while a save from the last few seconds is still being processed. Either is safe to use.

Keeping in sync

There are no webhooks. Two patterns work well:

  • Read on demand. Fetch the user's templates when your page that lists them loads, without caching, so a save made in the editor is visible immediately.
  • Poll with updated_after. Periodically list templates changed since your last check and mirror them into your own database.

Limits and errors

Backend requests are limited to 60 per minute and 2000 per day per plugin. Above that the API answers 429 with a Retry-After header.

Statusresult.errorMeaning
401credentials_requiredOne or both headers missing.
401invalid_credentialsThe pair does not match a plugin.
401credentials_in_queryCredentials were sent as query parameters.
409plugin_not_provisionedThe plugin is still being set up. Retry shortly.
404template_not_foundNo template with that id belongs to this plugin.

On this page