Dominios
Alta, registros DNS, verificación y reclamación de dominios.
Registra los dominios desde los que envías (y, si quieres, en los que recibes), consulta los registros DNS que hay que crear y sigue la verificación. El paso a paso del DNS está en Dominios y DNS.
#Listar dominios
GET/domains
Lista los dominios del equipo, del más reciente al más antiguo. La lista no trae los registros DNS. Los campos del dominio están en Estado, registros y eventos.
Parámetros de consulta
limitintegerDe 1 a 100. Predeterminado: 20.afterstringCursor de la página siguiente.beforestringCursor de la página anterior.searchstringBúsqueda por nombre.statusstringUno o más estados separados por comas:not_started,pending,verified,partially_verified,partially_failed,failed.regionstringSolo dominios de una región.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál.
cURL
curl 'https://api.postal.so/domains?status=verified' \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/domains?status=verified', {
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/domains",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
params={
"status": "verified",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('domains', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'query' => [
'status' => 'verified',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"object": "domain",
"id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
"paused_by_plan": false,
"name": "lojaexemplo.com.br",
"status": "verified",
"awaiting_confirmation": false,
"created_at": "2026-10-02T18:20:00.000000Z",
"region": "sa-east-1",
"dns_provider": "cloudflare",
"open_tracking": false,
"click_tracking": false,
"tracking_subdomain": null,
"tls": "opportunistic",
"capabilities": {
"sending": "enabled",
"receiving": "disabled"
},
"records_missing": null
}
]
}
#Crear un dominio
POST/domains
Registra el dominio, genera la clave DKIM y devuelve los registros DNS para que los crees en tu proveedor. Después, llama a POST /domains/{id}/verify para iniciar la verificación. Si el dominio ya está registrado en otra cuenta, usa la reclamación. Los registros también se envían por correo electrónico a quien registró el dominio (el usuario del panel o el creador de la clave), y se dispara el webhook domain.created. Para enviar los registros a otra persona, como el equipo de TI, usa POST /domains/{id}/instructions.
Cuerpo de la solicitud
namestringobligatorioNombre del dominio, comolojaexemplo.com.bromail.lojaexemplo.com.br. Debe ser único en Postal.regionstringRegión de envío:sa-east-1(São Paulo, por defecto),us-east-1,us-east-2,eu-west-1oap-northeast-1.custom_return_pathstringSubdominio del Return-Path (MAIL FROM). Por defecto:postal. Elegir otro depende del recurso de registros propios del plan.open_trackingbooleanRastrear aperturas. Por defecto:false. Activarlo depende del recurso de rastreo del plan.click_trackingbooleanRastrear clics. Por defecto:false. Activarlo depende del recurso de rastreo del plan.tracking_subdomainstringSubdominio propio para los enlaces rastreados, comolinks. Genera un registro CNAME. Depende del recurso de registros propios del plan.tlsstringopportunistic(por defecto) usa TLS cuando el servidor del destinatario lo acepta;enforcedsolo entrega con TLS.capabilitiesobjectQué hace el dominio.sendingstringenabled(por defecto) odisabled. Al menos una de las dos capacidades debe quedar activada.receivingstringenabledodisabled(por defecto). La recepción en tu dominio es de los planes de pago.
Errores
- 422
validation_errorNombre no válido o ya registrado, región no válida, o el dominio ya está configurado en el proveedor de envío por otro sistema (usa un subdominio). También cuando ninguna capacidad queda activada. - 403
plan_limit_reachedEl plan no permite más dominios. También cuando el plan no incluye lo solicitado: rastreo de aperturas y clics, Return-Path o subdominio de rastreo propios, o recibir en tu dominio.
cURL
curl -X POST https://api.postal.so/domains \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "lojaexemplo.com.br",
"region": "sa-east-1"
}'
Node.js
const response = await fetch('https://api.postal.so/domains', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'lojaexemplo.com.br',
region: 'sa-east-1',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/domains",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"name": "lojaexemplo.com.br",
"region": "sa-east-1",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('domains', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'name' => 'lojaexemplo.com.br',
'region' => 'sa-east-1',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "domain",
"id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
"paused_by_plan": false,
"name": "lojaexemplo.com.br",
"status": "not_started",
"awaiting_confirmation": false,
"created_at": "2026-10-02T18:20:00.000000Z",
"region": "sa-east-1",
"dns_provider": null,
"open_tracking": false,
"click_tracking": false,
"tracking_subdomain": null,
"tls": "opportunistic",
"capabilities": {
"sending": "enabled",
"receiving": "disabled"
},
"records_missing": null,
"records": [
{
"record": "DKIM",
"name": "postal._domainkey",
"type": "TXT",
"ttl": "Auto",
"status": "not_started",
"value": "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr4k…IDAQAB",
"diagnostic": null,
"last_checked_at": null
},
{
"record": "SPF",
"name": "postal",
"type": "MX",
"ttl": "Auto",
"status": "not_started",
"value": "feedback-smtp.sa-east-1.amazonses.com",
"priority": 10,
"diagnostic": null,
"last_checked_at": null
},
{
"record": "SPF",
"name": "postal",
"type": "TXT",
"ttl": "Auto",
"status": "not_started",
"value": "v=spf1 include:_spf.postal.so ~all",
"diagnostic": null,
"last_checked_at": null
}
]
}
#Obtener un dominio
GET/domains/{id}
Devuelve el dominio con los registros DNS (cada uno con su propio status y un diagnostic cuando algo falla) y el historial de eventos. Lo que significa cada estado, registro y evento está en Estado, registros y eventos.
Parámetros de ruta
idstringobligatorioID del dominio.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f', {
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/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
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('domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "domain",
"id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
"paused_by_plan": false,
"name": "lojaexemplo.com.br",
"status": "verified",
"awaiting_confirmation": false,
"created_at": "2026-10-02T18:20:00.000000Z",
"region": "sa-east-1",
"dns_provider": "cloudflare",
"open_tracking": false,
"click_tracking": false,
"tracking_subdomain": null,
"tls": "opportunistic",
"capabilities": {
"sending": "enabled",
"receiving": "disabled"
},
"records_missing": null,
"records": [
{
"record": "DKIM",
"name": "postal._domainkey",
"type": "TXT",
"ttl": "Auto",
"status": "verified",
"value": "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr4k…IDAQAB",
"diagnostic": null,
"last_checked_at": "2026-10-02T18:31:44.000000Z"
},
{
"record": "SPF",
"name": "postal",
"type": "MX",
"ttl": "Auto",
"status": "verified",
"value": "feedback-smtp.sa-east-1.amazonses.com",
"priority": 10,
"diagnostic": null,
"last_checked_at": "2026-10-02T18:31:44.000000Z"
},
{
"record": "SPF",
"name": "postal",
"type": "TXT",
"ttl": "Auto",
"status": "verified",
"value": "v=spf1 include:_spf.postal.so ~all",
"diagnostic": null,
"last_checked_at": "2026-10-02T18:31:44.000000Z"
}
],
"events": [
{
"type": "domain.created",
"occurred_at": "2026-10-02T18:20:00.000000Z",
"data": null
},
{
"type": "domain.verification_started",
"occurred_at": "2026-10-02T18:22:10.000000Z",
"data": null
},
{
"type": "domain.verified",
"occurred_at": "2026-10-02T18:31:44.000000Z",
"data": null
}
]
}
#Actualizar un dominio
PATCH/domains/{id}
Activa o desactiva el rastreo, el TLS obligatorio, el envío y la recepción. Cambiar las capacidades o el subdominio de rastreo cambia los registros DNS: consulta el dominio de nuevo para ver qué crear. Dispara el webhook domain.updated. No se puede cambiar el nombre, la región ni el Return-Path de un dominio: crea otro. Un dominio en pausa por el plan no se puede modificar.
Parámetros de ruta
idstringobligatorioID del dominio.
Cuerpo de la solicitud
open_trackingbooleanRastrear aperturas.click_trackingbooleanRastrear clics.tracking_subdomainstring | nullSubdominio de los enlaces rastreados.tlsstringopportunisticoenforced.capabilitiesobject{"sending": "enabled" | "disabled", "receiving": "enabled" | "disabled"}.
Errores
- 422
validation_errorAlgún campo no es válido. El mensaje indica cuál. - 403
plan_limit_reachedLa recepción en tu dominio no está en el plan. También cuando el plan no incluye el rastreo de aperturas y clics o el subdominio de rastreo propio. - 403
paused_by_planEl dominio está en pausa porque supera el límite de dominios del plan. - 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X PATCH https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"open_tracking": true,
"click_tracking": true,
"capabilities": {
"receiving": "enabled"
}
}'
Node.js
const response = await fetch('https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
open_tracking: true,
click_tracking: true,
capabilities: {
receiving: 'enabled',
},
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.patch(
"https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"open_tracking": True,
"click_tracking": True,
"capabilities": {
"receiving": "enabled",
},
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->patch('domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'open_tracking' => true,
'click_tracking' => true,
'capabilities' => [
'receiving' => 'enabled',
],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "domain",
"id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f"
}
#Eliminar un dominio
DELETE/domains/{id}
Quita el dominio. Los envíos desde él pasan a fallar. Dispara el webhook domain.deleted.
Parámetros de ruta
idstringobligatorioID del dominio.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X DELETE https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f', {
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/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
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('domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "domain",
"id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
"deleted": true
}
#Verificar un dominio
POST/domains/{id}/verify
Inicia (o reinicia) la verificación de los registros DNS en segundo plano. La respuesta es inmediata; sigue el resultado con GET /domains/{id} o con el webhook domain.updated. La verificación continúa sola durante hasta 72 horas. En un dominio ya verificado que perdió un registro, la llamada solo pide una comprobación inmediata, y el dominio sigue enviando mientras tanto.
Parámetros de ruta
idstringobligatorioID del dominio.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X POST https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f/verify \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f/verify', {
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/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f/verify",
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('domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f/verify', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "domain",
"id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f"
}
#Comprobar el MX antes de recibir
GET/domains/{id}/receiving-check
Antes de activar capabilities.receiving, averigua si el dominio ya tiene registros MX de otro proveedor (Google Workspace, Microsoft 365…). Competirían con el MX de Postal, y parte de los correos electrónicos seguiría yendo al buzón anterior. Postal consulta el DNS en el momento (con un tiempo de espera corto, y guarda la respuesta durante cerca de 1 minuto) y devuelve en mx_conflict los registros que no son de Postal y la sugerencia de usar un subdominio. Es solo información: no se bloquea nada y PATCH /domains/{id} funciona igual. Úsalo para mostrar un aviso antes de activar.
Parámetros de ruta
idstringobligatorioID del dominio.
mx_conflict:
| Campo | Descripción |
|---|---|
has_other_mx | true cuando el dominio tiene algún MX que no es el de Postal (el MX de Postal, mx.postal.so, nunca cuenta como conflicto). |
records | Los MX de otros proveedores, del menor al mayor priority: {"priority", "host"}. Un MX nulo (RFC 7505) aparece con host ".". |
suggestion | "subdomain" cuando hay conflicto (recibe en un subdominio, sin tocar el MX del dominio principal); si no, null. |
suggested_domain | El nombre sugerido, como entrada.lojaexemplo.com.br (inbound.… en inglés, según el Accept-Language), o null si no hay sugerencia. Regístralo con POST /domains. |
Si no se puede consultar el DNS en ese momento (fallo o tiempo agotado), la llamada sigue devolviendo 200 y mx_conflict llega como null: no muestres aviso e inténtalo de nuevo más tarde. El campo receiving indica si la recepción del dominio ya está activada.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f/receiving-check \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f/receiving-check', {
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/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f/receiving-check",
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('domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f/receiving-check', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "domain_receiving_check",
"domain_id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
"receiving": "disabled",
"mx_conflict": {
"has_other_mx": true,
"records": [
{
"priority": 1,
"host": "aspmx.l.google.com"
},
{
"priority": 5,
"host": "alt1.aspmx.l.google.com"
}
],
"suggestion": "subdomain",
"suggested_domain": "entrada.lojaexemplo.com.br"
}
}
#Reclamar un dominio
POST/domains/claim
Cuando el dominio ya está registrado en otra cuenta de Postal (por ejemplo, la de un exempleado), demuestra que controlas el DNS para transferirlo. El cuerpo es el mismo de POST /domains. Crea el registro TXT devuelto en record y llama a la verificación. La solicitud es válida durante 7 días; repetir la misma solicitud devuelve la que ya está abierta (estado 200).
Cuerpo de la solicitud
namestringobligatorioDominio registrado en otra cuenta.…Los demás campos dePOST /domains, que se aplican cuando se transfiere el dominio.
Errores
- 422
validation_errorEl dominio no está en otra cuenta (créalo conPOST /domains) o ya es tuyo. - 403
plan_limit_reachedEl plan no permite más dominios, o no incluye lo que pide el cuerpo (seguimiento, registros propios).
cURL
curl -X POST https://api.postal.so/domains/claim \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "lojaexemplo.com.br"
}'
Node.js
const response = await fetch('https://api.postal.so/domains/claim', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'lojaexemplo.com.br',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/domains/claim",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"name": "lojaexemplo.com.br",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('domains/claim', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'name' => 'lojaexemplo.com.br',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "domain_claim",
"id": "019a0a10-5c6d-7e7f-8a9b-0c1d2e3f4a5b",
"name": "lojaexemplo.com.br",
"status": "pending",
"domain_id": "019a0a10-5c6e-7f80-9a1b-2c3d4e5f6a7b",
"region": "sa-east-1",
"record": {
"type": "TXT",
"name": "lojaexemplo.com.br",
"value": "postal-domain-verification=Qm8rT2xKp4Vn7sWd1Yc5Hf3Lz9Ag6Ej0",
"ttl": "Auto"
},
"blocked_reason": null,
"failure_reason": null,
"created_at": "2026-10-24T13:10:00.000000Z",
"expires_at": "2026-10-31T13:10:00.000000Z"
}
#Obtener una reclamación
GET/domains/{domain_id}/claim
Muestra el progreso. status puede ser pending, verified, completed (el dominio ya es tuyo, con el ID domain_id), blocked (por ejemplo, hay correos electrónicos programados en la otra cuenta: blocked_reason lo explica), expired, superseded, canceled o failed.
Parámetros de ruta
domain_idstringobligatorioEldomain_iddevuelto al crear la reclamación.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl https://api.postal.so/domains/019a0a10-5c6e-7f80-9a1b-2c3d4e5f6a7b/claim \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/domains/019a0a10-5c6e-7f80-9a1b-2c3d4e5f6a7b/claim', {
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/domains/019a0a10-5c6e-7f80-9a1b-2c3d4e5f6a7b/claim",
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('domains/019a0a10-5c6e-7f80-9a1b-2c3d4e5f6a7b/claim', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "domain_claim",
"id": "019a0a10-5c6d-7e7f-8a9b-0c1d2e3f4a5b",
"name": "lojaexemplo.com.br",
"status": "pending",
"domain_id": "019a0a10-5c6e-7f80-9a1b-2c3d4e5f6a7b",
"region": "sa-east-1",
"record": {
"type": "TXT",
"name": "lojaexemplo.com.br",
"value": "postal-domain-verification=Qm8rT2xKp4Vn7sWd1Yc5Hf3Lz9Ag6Ej0",
"ttl": "Auto"
},
"blocked_reason": null,
"failure_reason": null,
"created_at": "2026-10-24T13:10:00.000000Z",
"expires_at": "2026-10-31T13:10:00.000000Z"
}
#Verificar una reclamación
POST/domains/{domain_id}/claim/verify
Busca el registro TXT y, si está, transfiere el dominio en segundo plano. Consulta la reclamación para ver el resultado; mientras el TXT no aparezca, failure_reason lo indica.
Parámetros de ruta
domain_idstringobligatorioEldomain_idde la reclamación.
Errores
- 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X POST https://api.postal.so/domains/019a0a10-5c6e-7f80-9a1b-2c3d4e5f6a7b/claim/verify \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/domains/019a0a10-5c6e-7f80-9a1b-2c3d4e5f6a7b/claim/verify', {
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/domains/019a0a10-5c6e-7f80-9a1b-2c3d4e5f6a7b/claim/verify",
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('domains/019a0a10-5c6e-7f80-9a1b-2c3d4e5f6a7b/claim/verify', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "domain_claim",
"id": "019a0a10-5c6d-7e7f-8a9b-0c1d2e3f4a5b",
"name": "lojaexemplo.com.br",
"status": "pending",
"domain_id": "019a0a10-5c6e-7f80-9a1b-2c3d4e5f6a7b",
"region": "sa-east-1",
"record": {
"type": "TXT",
"name": "lojaexemplo.com.br",
"value": "postal-domain-verification=Qm8rT2xKp4Vn7sWd1Yc5Hf3Lz9Ag6Ej0",
"ttl": "Auto"
},
"blocked_reason": null,
"failure_reason": null,
"created_at": "2026-10-24T13:10:00.000000Z",
"expires_at": "2026-10-31T13:10:00.000000Z"
}
#Enviar las instrucciones por correo electrónico
POST/domains/{id}/instructions
Envía por correo electrónico los registros DNS del dominio y el paso a paso del proveedor de DNS a un máximo de 5 direcciones, como el equipo de TI o la agencia que gestiona tu DNS. Quien lo recibe no necesita una cuenta de Postal: el correo trae todo e indica quién lo pidió. Es un correo del sistema, que no cuenta en la cuota de envío.
Parámetros de ruta
idstringobligatorioID del dominio.
Cuerpo de la solicitud
tostring[]obligatorioDe 1 a 5 direcciones de correo, de hasta 254 caracteres cada una. Las repetidas cuentan una sola vez.messagestringMensaje que va en el correo, de hasta 500 caracteres y sin enlaces (el correo ya trae todo lo que la persona necesita).
Errores
- 422
validation_errortovacío, con más de 5 direcciones o con una dirección no válida, omessagedemasiado largo o con enlace. - 429
rate_limit_exceededMás de 3 solicitudes por minuto o 10 por hora en el equipo, o una de las direcciones ya recibió las instrucciones 5 veces hoy. - 404
not_foundEl recurso no existe o es de otro equipo.
cURL
curl -X POST https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f/instructions \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": ["[email protected]"],
"message": "Oi! Pode criar esses registros no DNS do domínio?"
}'
Node.js
const response = await fetch('https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f/instructions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: ['[email protected]'],
message: 'Oi! Pode criar esses registros no DNS do domínio?',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f/instructions",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"to": ["[email protected]"],
"message": "Oi! Pode criar esses registros no DNS do domínio?",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('domains/0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f/instructions', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'to' => ['[email protected]'],
'message' => 'Oi! Pode criar esses registros no DNS do domínio?',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "domain",
"id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
"sent_to": ["[email protected]"]
}
#Estado, registros y eventos
El objeto dominio incluye, además del nombre y la región, el estado de la verificación y de los registros DNS.
| Campo | Descripción |
|---|---|
status | El estado de la verificación: not_started (creado, aún sin verificación), pending (en verificación, hasta 72 horas), partially_verified (solo se encontró parte de los registros), verified (puede enviar), partially_failed (el plazo terminó con parte de los registros encontrada) y failed (el plazo terminó sin ningún registro). |
awaiting_confirmation | true cuando ya se encontraron todos los registros necesarios para enviar y solo falta la confirmación final del proveedor de envío, que suele tardar de 5 a 30 minutos. |
capabilities | sending y receiving, cada uno enabled o disabled. Solo envía un dominio verificado y con sending activado. |
dns_provider | El proveedor de DNS detectado (como cloudflare), o null. |
records_missing | null o, en un dominio que ya funcionaba y perdió un registro obligatorio, {"since", "deadline_at", "records"}: desde cuándo falta, el plazo y los registros (record, type, name). El dominio sigue enviando hasta el plazo, de 72 horas, y Postal busca el registro cada hora; si vuelve, el aviso desaparece, y si el plazo se agota, el dominio deja de estar verificado. |
paused_by_plan | true cuando el dominio supera el límite de dominios del plan. Sigue en la lista, pero no se puede modificar y no envía: consulta Límites del plan. |
Registros DNS
Cada elemento de records tiene record (el tipo de registro de Postal), type (el tipo de DNS), name, ttl, value, priority (solo en los MX), status, diagnostic y last_checked_at. El diagnostic es un texto para personas que explica lo que encontró la última comprobación (null cuando el registro es correcto) y viene en el idioma de la solicitud: Accept-Language: pt-BR o en, en inglés sin el encabezado. No lo compares con el texto: decide por el status.
| record | Registros | Para qué sirve |
|---|---|---|
DKIM | TXT en postal._domainkey | La firma de los correos. Obligatorio para enviar. |
SPF | MX y TXT en el subdominio del Return-Path (por defecto postal) | Recibe rebotes y quejas y autoriza a Postal a enviar. Obligatorio para enviar. |
Tracking | CNAME en tracking_subdomain | Enlaces de seguimiento en tu dominio. Solo existe con el seguimiento activado y un subdominio de seguimiento; no bloquea el envío. |
Receiving | MX en @ | Entrega los correos recibidos en los buzones de entrada. Solo con capabilities.receiving activado. |
El status de un registro es not_started, pending, verified, failed o temporary_failure (falló la consulta al DNS; Postal lo intenta de nuevo). Un dominio verificado se vuelve a comprobar todos los días.
Eventos del dominio
El events de GET /domains/{id} cuenta la historia del dominio, del más antiguo al más reciente:
| Tipo | Cuándo |
|---|---|
domain.created | El dominio se registró. |
domain.verification_started | La verificación empezó o volvió a empezar. |
domain.dns_verified | Se encontraron todos los registros. |
domain.verified | El dominio se verificó y puede enviar. |
domain.partially_failed, domain.failed | El plazo de verificación terminó con parte de los registros o sin ninguno. |
domain.tracking_updated | Cambió el seguimiento o el subdominio de seguimiento. |
domain.records_missing | Un registro obligatorio desapareció de un dominio que ya funcionaba. |
domain.records_restored | Volvió el registro que faltaba. |
domain.verification_lost | El registro no volvió dentro del plazo y el dominio dejó de estar verificado. |
domain.provider_error | Hubo un error al comunicarse con el proveedor de envío (data.stage y data.code); Postal lo intenta de nuevo. |
Los cambios de estado también llegan por webhook: domain.created, domain.updated y domain.deleted (consulta Webhooks).
#Con los SDK
Los SDK oficiales tienen domains.list, domains.get, domains.create y domains.verify, con los campos de la API.
Node.js
import { Postal } from 'postal-so'
const postal = new Postal() // lê POSTAL_API_KEY
const domain = await postal.domains.create({ name: 'lojaexemplo.com.br', region: 'sa-east-1' })
console.log(domain.records) // os registros DNS para criar
await postal.domains.verify(domain.id)
const current = await postal.domains.get(domain.id) // status e registros
PHP
use Postal\Client;
$postal = new Client(); // lê POSTAL_API_KEY
$domain = $postal->domains->create(['name' => 'lojaexemplo.com.br', 'region' => 'sa-east-1']);
$records = $domain['records']; // os registros DNS para criar
$postal->domains->verify($domain['id']);
$current = $postal->domains->get($domain['id']); // status e registros