Iniciar sesión Crear cuenta
Referencia de la API

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

Clave de acceso total

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

  • limitinteger
    De 1 a 100. Predeterminado: 20.
  • afterstring
    Cursor de la página siguiente.
  • beforestring
    Cursor de la página anterior.
  • searchstring
    Búsqueda por nombre.
  • statusstring
    Uno o más estados separados por comas: not_started, pending, verified, partially_verified, partially_failed, failed.
  • regionstring
    Solo dominios de una región.

Errores

  • 422validation_error
    Algún campo no es válido. El mensaje indica cuál.
GET /domains

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);
200Respuesta
{
  "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

Clave de acceso total

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

  • namestringobligatorio
    Nombre del dominio, como lojaexemplo.com.br o mail.lojaexemplo.com.br. Debe ser único en Postal.
  • regionstring
    Región de envío: sa-east-1 (São Paulo, por defecto), us-east-1, us-east-2, eu-west-1 o ap-northeast-1.
  • custom_return_pathstring
    Subdominio del Return-Path (MAIL FROM). Por defecto: postal. Elegir otro depende del recurso de registros propios del plan.
  • open_trackingboolean
    Rastrear aperturas. Por defecto: false. Activarlo depende del recurso de rastreo del plan.
  • click_trackingboolean
    Rastrear clics. Por defecto: false. Activarlo depende del recurso de rastreo del plan.
  • tracking_subdomainstring
    Subdominio propio para los enlaces rastreados, como links. Genera un registro CNAME. Depende del recurso de registros propios del plan.
  • tlsstring
    opportunistic (por defecto) usa TLS cuando el servidor del destinatario lo acepta; enforced solo entrega con TLS.
  • capabilitiesobject
    Qué hace el dominio.
    • sendingstring
      enabled (por defecto) o disabled. Al menos una de las dos capacidades debe quedar activada.
    • receivingstring
      enabled o disabled (por defecto). La recepción en tu dominio es de los planes de pago.

Errores

  • 422validation_error
    Nombre 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.
  • 403plan_limit_reached
    El 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.
POST /domains

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);
201Respuesta
{
  "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}

Clave de acceso total

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

  • idstringobligatorio
    ID del dominio.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
GET /domains/{id}

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);
200Respuesta
{
  "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}

Clave de acceso total

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

  • idstringobligatorio
    ID del dominio.

Cuerpo de la solicitud

  • open_trackingboolean
    Rastrear aperturas.
  • click_trackingboolean
    Rastrear clics.
  • tracking_subdomainstring | null
    Subdominio de los enlaces rastreados.
  • tlsstring
    opportunistic o enforced.
  • capabilitiesobject
    {"sending": "enabled" | "disabled", "receiving": "enabled" | "disabled"}.

Errores

  • 422validation_error
    Algún campo no es válido. El mensaje indica cuál.
  • 403plan_limit_reached
    La 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.
  • 403paused_by_plan
    El dominio está en pausa porque supera el límite de dominios del plan.
  • 404not_found
    El recurso no existe o es de otro equipo.
PATCH /domains/{id}

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);
200Respuesta
{
  "object": "domain",
  "id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f"
}

#Eliminar un dominio

DELETE/domains/{id}

Clave de acceso total

Quita el dominio. Los envíos desde él pasan a fallar. Dispara el webhook domain.deleted.

Parámetros de ruta

  • idstringobligatorio
    ID del dominio.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
DELETE /domains/{id}

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);
200Respuesta
{
  "object": "domain",
  "id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f",
  "deleted": true
}

#Verificar un dominio

POST/domains/{id}/verify

Clave de acceso total

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

  • idstringobligatorio
    ID del dominio.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
POST /domains/{id}/verify

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);
200Respuesta
{
  "object": "domain",
  "id": "0199f0e1-2a3b-7c4d-8e5f-6a7b8c9d0e1f"
}

#Comprobar el MX antes de recibir

GET/domains/{id}/receiving-check

Clave de acceso total

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

  • idstringobligatorio
    ID del dominio.

mx_conflict:

CampoDescripción
has_other_mxtrue cuando el dominio tiene algún MX que no es el de Postal (el MX de Postal, mx.postal.so, nunca cuenta como conflicto).
recordsLos 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_domainEl 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

  • 404not_found
    El recurso no existe o es de otro equipo.
GET /domains/{id}/receiving-check

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);
200Respuesta
{
  "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

Clave de acceso total

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

  • namestringobligatorio
    Dominio registrado en otra cuenta.
  • …
    Los demás campos de POST /domains, que se aplican cuando se transfiere el dominio.

Errores

  • 422validation_error
    El dominio no está en otra cuenta (créalo con POST /domains) o ya es tuyo.
  • 403plan_limit_reached
    El plan no permite más dominios, o no incluye lo que pide el cuerpo (seguimiento, registros propios).
POST /domains/claim

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);
201Respuesta
{
  "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

Clave de acceso total

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_idstringobligatorio
    El domain_id devuelto al crear la reclamación.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
GET /domains/{domain_id}/claim

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);
200Respuesta
{
  "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

Clave de acceso total

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_idstringobligatorio
    El domain_id de la reclamación.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
POST /domains/{domain_id}/claim/verify

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);
200Respuesta
{
  "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

Clave de acceso total

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

  • idstringobligatorio
    ID del dominio.

Cuerpo de la solicitud

  • tostring[]obligatorio
    De 1 a 5 direcciones de correo, de hasta 254 caracteres cada una. Las repetidas cuentan una sola vez.
  • messagestring
    Mensaje que va en el correo, de hasta 500 caracteres y sin enlaces (el correo ya trae todo lo que la persona necesita).

Errores

  • 422validation_error
    to vacío, con más de 5 direcciones o con una dirección no válida, o message demasiado largo o con enlace.
  • 429rate_limit_exceeded
    Má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.
  • 404not_found
    El recurso no existe o es de otro equipo.
POST /domains/{id}/instructions

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);
200Respuesta
{
  "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.

CampoDescripción
statusEl 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_confirmationtrue 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.
capabilitiessending y receiving, cada uno enabled o disabled. Solo envía un dominio verificado y con sending activado.
dns_providerEl proveedor de DNS detectado (como cloudflare), o null.
records_missingnull 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_plantrue 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.

recordRegistrosPara qué sirve
DKIMTXT en postal._domainkeyLa firma de los correos. Obligatorio para enviar.
SPFMX y TXT en el subdominio del Return-Path (por defecto postal)Recibe rebotes y quejas y autoriza a Postal a enviar. Obligatorio para enviar.
TrackingCNAME en tracking_subdomainEnlaces de seguimiento en tu dominio. Solo existe con el seguimiento activado y un subdominio de seguimiento; no bloquea el envío.
ReceivingMX 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:

TipoCuándo
domain.createdEl dominio se registró.
domain.verification_startedLa verificación empezó o volvió a empezar.
domain.dns_verifiedSe encontraron todos los registros.
domain.verifiedEl dominio se verificó y puede enviar.
domain.partially_failed, domain.failedEl plazo de verificación terminó con parte de los registros o sin ninguno.
domain.tracking_updatedCambió el seguimiento o el subdominio de seguimiento.
domain.records_missingUn registro obligatorio desapareció de un dominio que ya funcionaba.
domain.records_restoredVolvió el registro que faltaba.
domain.verification_lostEl registro no volvió dentro del plazo y el dominio dejó de estar verificado.
domain.provider_errorHubo 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.

Crear y verificar un dominio

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