Templates
Templates com variáveis, rascunho, versões publicadas, testes e imagens.
Templates guardam o conteúdo de e-mails que você envia sempre, com variáveis. Você edita um rascunho e publica quando estiver pronto: os envios usam sempre a última versão publicada, então editar o rascunho não muda nada em produção. Para enviar com um template, use o campo template em POST /emails. Em todos os endpoints abaixo, {id} aceita o ID ou o alias.
#Listar templates
GET/templates
Parâmetros de consulta
limitintegerDe 1 a 100. Padrão: 20.afterstringCursor da próxima página.beforestringCursor da página anterior.searchstringBusca no nome e no alias.statusstringpublishedoudraft(nunca publicados).
cURL
curl 'https://api.postal.so/templates?status=published' \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/templates?status=published', {
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.get(
"https://api.postal.so/templates",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
params={
"status": "published",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('templates', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'query' => [
'status' => 'published',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"object": "template",
"id": "0199f3a0-1b2c-7d3e-9f4a-5b6c7d8e9f0a",
"name": "Pedido enviado",
"alias": "order-shipped",
"status": "published",
"published_at": "2026-10-10T15:00:00.000000Z",
"created_at": "2026-10-08T11:30:00.000000Z",
"updated_at": "2026-10-10T14:58:12.000000Z",
"has_unpublished_versions": false,
"current_version_id": "0199f3a0-8c9d-7e0f-a1b2-c3d4e5f6a7b8"
}
]
}
#Criar um template
POST/templates
Cria o template como rascunho. Publique com POST /templates/{id}/publish antes de usar nos envios.
Corpo da requisição
namestringobrigatórioNome interno, até 255 caracteres. Obrigatório na criação.htmlstringobrigatórioHTML do e-mail, até 2 MB, com variáveis no formato{{{NAME}}}. Obrigatório na criação.aliasstring | nullApelido único para usar no lugar do ID, comoorder-shipped: letras, números,.,-e_, até 100 caracteres.fromstring | nullRemetente padrão.subjectstring | nullAssunto padrão; aceita variáveis.reply_tostring | string[] | nullEndereços de resposta padrão.textstring | nullVersão em texto. Sem ela, a Postal gera o texto a partir do HTML no envio.variablesobject[] | nullAté 50 variáveis declaradas.keystringobrigatórioNome da variável: letras, números e_, até 50 caracteres.typestringstring(padrão) ounumber.fallback_valuestring | number | nullValor usado quando o envio não informa a variável. Sem ele, a variável é obrigatória.
editorobject | nullEstado do editor visual do painel. Pode ignorar.
Erros
- 422
validation_errorCampo inválido, alias já usado ou variável repetida.
cURL
curl -X POST https://api.postal.so/templates \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Pedido enviado",
"alias": "order-shipped",
"from": "Loja Exemplo <[email protected]>",
"subject": "Seu pedido #{{{ORDER}}} saiu para entrega",
"html": "<p>Olá, {{{NAME}}}! O pedido <b>#{{{ORDER}}}</b> está a caminho.</p>",
"variables": [
{
"key": "NAME",
"fallback_value": "cliente"
},
{
"key": "ORDER",
"type": "number"
}
]
}'
Node.js
const response = await fetch('https://api.postal.so/templates', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Pedido enviado',
alias: 'order-shipped',
from: 'Loja Exemplo <[email protected]>',
subject: 'Seu pedido #{{{ORDER}}} saiu para entrega',
html: '<p>Olá, {{{NAME}}}! O pedido <b>#{{{ORDER}}}</b> está a caminho.</p>',
variables: [
{
key: 'NAME',
fallback_value: 'cliente',
},
{
key: 'ORDER',
type: 'number',
},
],
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/templates",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"name": "Pedido enviado",
"alias": "order-shipped",
"from": "Loja Exemplo <[email protected]>",
"subject": "Seu pedido #{{{ORDER}}} saiu para entrega",
"html": "<p>Olá, {{{NAME}}}! O pedido <b>#{{{ORDER}}}</b> está a caminho.</p>",
"variables": [
{
"key": "NAME",
"fallback_value": "cliente",
},
{
"key": "ORDER",
"type": "number",
},
],
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('templates', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'name' => 'Pedido enviado',
'alias' => 'order-shipped',
'from' => 'Loja Exemplo <[email protected]>',
'subject' => 'Seu pedido #{{{ORDER}}} saiu para entrega',
'html' => '<p>Olá, {{{NAME}}}! O pedido <b>#{{{ORDER}}}</b> está a caminho.</p>',
'variables' => [
[
'key' => 'NAME',
'fallback_value' => 'cliente',
],
[
'key' => 'ORDER',
'type' => 'number',
],
],
],
]);
$data = json_decode($response->getBody(), true);
{
"id": "0199f3a0-1b2c-7d3e-9f4a-5b6c7d8e9f0a",
"object": "template"
}
#Obter um template
GET/templates/{id}
Devolve o template com o conteúdo do rascunho. has_unpublished_versions indica se o rascunho difere da versão publicada.
Parâmetros de caminho
idstringobrigatórioID ou alias.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl https://api.postal.so/templates/order-shipped \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/templates/order-shipped', {
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.get(
"https://api.postal.so/templates/order-shipped",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('templates/order-shipped', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "template",
"id": "0199f3a0-1b2c-7d3e-9f4a-5b6c7d8e9f0a",
"name": "Pedido enviado",
"alias": "order-shipped",
"status": "published",
"published_at": "2026-10-10T15:00:00.000000Z",
"created_at": "2026-10-08T11:30:00.000000Z",
"updated_at": "2026-10-10T14:58:12.000000Z",
"has_unpublished_versions": false,
"current_version_id": "0199f3a0-8c9d-7e0f-a1b2-c3d4e5f6a7b8",
"from": "Loja Exemplo <[email protected]>",
"subject": "Seu pedido #{{{ORDER}}} saiu para entrega",
"reply_to": ["[email protected]"],
"html": "<p>Olá, {{{NAME}}}! O pedido <b>#{{{ORDER}}}</b> está a caminho.</p>",
"text": null,
"variables": [
{
"key": "NAME",
"type": "string",
"fallback_value": "cliente"
},
{
"key": "ORDER",
"type": "number",
"fallback_value": null
}
],
"editor": null
}
#Atualizar um template
PATCH/templates/{id}
Altera o rascunho. Todos os campos são opcionais; os que você não enviar ficam como estão. A versão publicada só muda quando você publicar de novo.
Parâmetros de caminho
idstringobrigatórioID ou alias.
Corpo da requisição (todos opcionais)
namestringNome interno, até 255 caracteres. Obrigatório na criação.htmlstringHTML do e-mail, até 2 MB, com variáveis no formato{{{NAME}}}. Obrigatório na criação.aliasstring | nullApelido único para usar no lugar do ID, comoorder-shipped: letras, números,.,-e_, até 100 caracteres.fromstring | nullRemetente padrão.subjectstring | nullAssunto padrão; aceita variáveis.reply_tostring | string[] | nullEndereços de resposta padrão.textstring | nullVersão em texto. Sem ela, a Postal gera o texto a partir do HTML no envio.variablesobject[] | nullAté 50 variáveis declaradas.keystringobrigatórioNome da variável: letras, números e_, até 50 caracteres.typestringstring(padrão) ounumber.fallback_valuestring | number | nullValor usado quando o envio não informa a variável. Sem ele, a variável é obrigatória.
editorobject | nullEstado do editor visual do painel. Pode ignorar.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X PATCH https://api.postal.so/templates/order-shipped \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subject": "Pedido #{{{ORDER}}} a caminho"
}'
Node.js
const response = await fetch('https://api.postal.so/templates/order-shipped', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
subject: 'Pedido #{{{ORDER}}} a caminho',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.patch(
"https://api.postal.so/templates/order-shipped",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"subject": "Pedido #{{{ORDER}}} a caminho",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->patch('templates/order-shipped', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'subject' => 'Pedido #{{{ORDER}}} a caminho',
],
]);
$data = json_decode($response->getBody(), true);
{
"id": "0199f3a0-1b2c-7d3e-9f4a-5b6c7d8e9f0a",
"object": "template"
}
#Excluir um template
DELETE/templates/{id}
Envios que usam o template passam a receber 404.
Parâmetros de caminho
idstringobrigatórioID ou alias.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X DELETE https://api.postal.so/templates/0199f3a0-1b2c-7d3e-9f4a-5b6c7d8e9f0a \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/templates/0199f3a0-1b2c-7d3e-9f4a-5b6c7d8e9f0a', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.delete(
"https://api.postal.so/templates/0199f3a0-1b2c-7d3e-9f4a-5b6c7d8e9f0a",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->delete('templates/0199f3a0-1b2c-7d3e-9f4a-5b6c7d8e9f0a', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "template",
"id": "0199f3a0-1b2c-7d3e-9f4a-5b6c7d8e9f0a",
"deleted": true
}
#Publicar um template
POST/templates/{id}/publish
Cria uma nova versão a partir do rascunho atual. A partir daí, os envios com este template usam essa versão.
Parâmetros de caminho
idstringobrigatórioID ou alias.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X POST https://api.postal.so/templates/order-shipped/publish \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/templates/order-shipped/publish', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/templates/order-shipped/publish",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('templates/order-shipped/publish', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"id": "0199f3a0-1b2c-7d3e-9f4a-5b6c7d8e9f0a",
"object": "template"
}
#Duplicar um template
POST/templates/{id}/duplicate
Cria uma cópia do rascunho com o nome "… (cópia)", sem alias e sem versões publicadas.
Parâmetros de caminho
idstringobrigatórioID ou alias.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X POST https://api.postal.so/templates/order-shipped/duplicate \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/templates/order-shipped/duplicate', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/templates/order-shipped/duplicate",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('templates/order-shipped/duplicate', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"id": "0199f3a2-4e5f-7a6b-8c7d-9e0f1a2b3c4d",
"object": "template"
}
#Enviar um teste
POST/templates/{id}/test
Envia o rascunho (publicado ou não) para até 5 endereços, com o assunto prefixado por [Teste]. O teste é um envio normal: conta na cota e aparece em GET /emails com a tag template_test.
Parâmetros de caminho
idstringobrigatórioID ou alias.
Corpo da requisição
tostring[]obrigatórioDe 1 a 5 endereços.fromstringRemetente; padrão: ofromdo template.variablesobjectValores das variáveis para o teste.
Erros
- 422
validation_errorEndereço inválido, remetente ausente ou variável obrigatória sem valor. - 403
validation_errorO domínio do remetente não está verificado. - 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X POST https://api.postal.so/templates/order-shipped/test \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": ["[email protected]"],
"variables": {
"NAME": "Rafaela",
"ORDER": 10482
}
}'
Node.js
const response = await fetch('https://api.postal.so/templates/order-shipped/test', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: ['[email protected]'],
variables: {
NAME: 'Rafaela',
ORDER: 10482,
},
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/templates/order-shipped/test",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"to": ["[email protected]"],
"variables": {
"NAME": "Rafaela",
"ORDER": 10482,
},
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('templates/order-shipped/test', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'to' => ['[email protected]'],
'variables' => [
'NAME' => 'Rafaela',
'ORDER' => 10482,
],
],
]);
$data = json_decode($response->getBody(), true);
{
"id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
"object": "email"
}
#Listar versões
GET/templates/{id}/versions
As versões publicadas, da mais recente para a mais antiga (até 100). current marca a que está em uso.
Parâmetros de caminho
idstringobrigatórioID ou alias.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl https://api.postal.so/templates/order-shipped/versions \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/templates/order-shipped/versions', {
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.get(
"https://api.postal.so/templates/order-shipped/versions",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('templates/order-shipped/versions', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"object": "template_version",
"id": "0199f3a0-8c9d-7e0f-a1b2-c3d4e5f6a7b8",
"version": 2,
"subject": "Seu pedido #{{{ORDER}}} saiu para entrega",
"published_at": "2026-10-10T15:00:00.000000Z",
"published_by": "Rafaela Lima",
"current": true
},
{
"object": "template_version",
"id": "0199f3a0-5a6b-7c7d-8e9f-0a1b2c3d4e5f",
"version": 1,
"subject": "Seu pedido saiu para entrega",
"published_at": "2026-10-08T12:10:00.000000Z",
"published_by": "Rafaela Lima",
"current": false
}
]
}
#Restaurar uma versão
POST/templates/{id}/versions/{version_id}/restore
Copia uma versão publicada de volta para o rascunho. Para colocá-la em uso, publique em seguida.
Parâmetros de caminho
idstringobrigatórioID ou alias.version_idstringobrigatórioID da versão.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl -X POST https://api.postal.so/templates/order-shipped/versions/0199f3a0-5a6b-7c7d-8e9f-0a1b2c3d4e5f/restore \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/templates/order-shipped/versions/0199f3a0-5a6b-7c7d-8e9f-0a1b2c3d4e5f/restore', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/templates/order-shipped/versions/0199f3a0-5a6b-7c7d-8e9f-0a1b2c3d4e5f/restore",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('templates/order-shipped/versions/0199f3a0-5a6b-7c7d-8e9f-0a1b2c3d4e5f/restore', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"id": "0199f3a0-1b2c-7d3e-9f4a-5b6c7d8e9f0a",
"object": "template"
}
#Enviar uma imagem
POST/assets
Hospeda uma imagem para usar nos seus e-mails. A URL devolvida é pública (os clientes de e-mail precisam carregá-la), permanente e difícil de adivinhar. Envie como multipart/form-data.
Corpo da requisição (multipart/form-data)
filearquivoobrigatórioPNG, JPG, GIF ou WebP, até 5 MB.
Erros
- 422
validation_errorArquivo ausente, de outro tipo ou maior que 5 MB.
cURL
curl -X POST https://api.postal.so/assets \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-F "[email protected]"
Node.js
import fs from 'node:fs/promises';
const form = new FormData();
form.append('file', new Blob([await fs.readFile('banner.png')]), 'banner.png');
const response = await fetch('https://api.postal.so/assets', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
body: form,
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/assets",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
files={"file": open("banner.png", "rb")},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('assets', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'multipart' => [
['name' => 'file', 'contents' => fopen('banner.png', 'r')],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "asset",
"id": "019a1a00-2b3c-7d4e-8f5a-6b7c8d9e0f1a",
"url": "https://api.postal.so/assets/019a1a00-2b3c-7d4e-8f5a-6b7c8d9e0f1a/banner.png",
"filename": "banner.png"
}