# DNS del correo

> Planifica los registros MX, SPF, DKIM y DMARC de tus proveedores de correo y comprueba los registros de correo de una zona.

El asistente de correo calcula los registros que necesita una zona para los proveedores de buzón y de envío elegidos (un único SPF, MX, DKIM, DMARC) y comprueba lo que muestra el DNS público. Estas rutas no aplican nada; un plan se aplica con [Lote de registros](https://developers.openwaf.dev/es/api/dns-records/#batch-records).

| Operación | Ruta |
|---|---|
| [Listar proveedores de correo](https://developers.openwaf.dev/es/api/email/#list-email-providers) | `GET /v1/orgs/{org}/zones/{zone}/email/providers` |
| [Planificar los registros de correo](https://developers.openwaf.dev/es/api/email/#plan-email-records) | `POST /v1/orgs/{org}/zones/{zone}/email/plan` |
| [Comprobar los registros de correo](https://developers.openwaf.dev/es/api/email/#check-email) | `GET /v1/orgs/{org}/zones/{zone}/email/check` |

## Listar proveedores de correo

```http
GET /v1/orgs/{org}/zones/{zone}/email/providers
```

Lista las plantillas de proveedores del asistente de correo, de buzón (`mailbox`, p. ej. Google Workspace, Microsoft 365, Zoho Mail, Proton Mail, Fastmail), de envío (`sending`, p. ej. Amazon SES, Resend, Mailgun, SendGrid, Brevo) y `other`, con los parámetros que pide cada una (con los valores por defecto ya calculados para la zona).

**Rol necesario:** `readonly` o superior.

#### Parámetros

| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `org` | ruta | string (uuid) | Sí | Id de la organización. Cada ruta comprueba que es la del token (si no, `404`). |
| `zone` | ruta | string (uuid) | Sí | Id de la zona (consulta [Listar zonas](https://developers.openwaf.dev/es/api/zones/#list-zones)). |

#### Respuesta

- `200`: Devuelve `items`, un array de [EmailProvider](https://developers.openwaf.dev/es/api/schemas/#email-provider).

| Campo | Tipo | Descripción |
|---|---|---|
| `items[].id` | string | Id. |
| `items[].name` | string | Nombre. |
| `items[].kind` | string | Uno de `mailbox`, `sending`, `other`. |
| `items[].docs` | string |  |
| `items[].spf` | array de string | Mecanismos que añade al SPF del dominio. |
| `items[].params` | array de object |  |
| `items[].params[].key` | string |  |
| `items[].params[].kind` | string | Uno de `text`, `dkim`, `cname`, `select`, `list`. |
| `items[].params[].required` | boolean |  |
| `items[].params[].default` | string |  |
| `items[].params[].selector` | string | Selector DKIM donde va el valor (kind dkim/cname). |
| `items[].params[].options` | array de string |  |
| `items[].params[].placeholder` | string |  |

#### Errores

| Estado | Descripción |
|---|---|
| `404` | No existe o el token no lo ve (`not_found`). |

#### Ejemplo

**curl**


```sh
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/email/providers" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token de OPENWAF_TOKEN
const result = await owf.listEmailProviders({
  zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token de OPENWAF_TOKEN
result = owf.list_email_providers(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d")
```



```json
{
  "items": [
    {
      "id": "example",
      "name": "example.com",
      "kind": "mailbox",
      "docs": "example",
      "spf": [
        "example"
      ],
      "params": [
        {}
      ]
    }
  ]
}
```

## Planificar los registros de correo

```http
POST /v1/orgs/{org}/zones/{zone}/email/plan
```

Calcula los registros de correo que necesita la zona para los proveedores elegidos, comparados con sus registros actuales (el DNS público en una zona externa): un único SPF que une los mecanismos de los proveedores con los existentes (conservando el `~all`/`-all` que hubiera), los MX del proveedor de buzón, las claves DKIM pegadas por el usuario (validadas), los registros de verificación, los subdominios de envío y DMARC (`p=none` por defecto). También cuenta las búsquedas DNS del SPF resultante (más de 10 es un error).

No aplica nada. Los problemas van en `issues` (los errores dejan `batch` nulo). `batch` se envía tal cual a [Lote de registros](https://developers.openwaf.dev/es/api/dns-records/#batch-records), primero con `dry_run=1`. En zonas externas `applicable` es falso y las acciones son `manual`.

**Rol necesario:** `readonly` o superior.

#### Parámetros

| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `org` | ruta | string (uuid) | Sí | Id de la organización. Cada ruta comprueba que es la del token (si no, `404`). |
| `zone` | ruta | string (uuid) | Sí | Id de la zona (consulta [Listar zonas](https://developers.openwaf.dev/es/api/zones/#list-zones)). |

#### Cuerpo de la petición

Tipo de contenido `application/json`. Esquema: [EmailPlanInput](https://developers.openwaf.dev/es/api/schemas/#email-plan-input).

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `providers` | array de object | Sí |  |
| `providers[].id` | string | Sí | Id. |
| `providers[].params` | mapa de string | No |  |
| `dmarc` | object o null | No | Nulo conserva el DMARC existente (o añade p=none si no hay). |
| `dmarc.policy` | string | No | Uno de `none`, `quarantine`, `reject`. |
| `dmarc.rua` | string | No | Dirección para los informes agregados: ausente conserva la del registro existente, vacía la quita. |
| `spf_all` | string | No | Uno de `""`, `~all`, `-all`. |

#### Respuesta

- `200`: Devuelve [EmailPlan](https://developers.openwaf.dev/es/api/schemas/#email-plan).

| Campo | Tipo | Descripción |
|---|---|---|
| `zone` | string |  |
| `dns_mode` | string | Uno de `delegated`, `external`. |
| `applicable` | boolean |  |
| `records` | array de object |  |
| `records[].type` | string |  |
| `records[].name` | string | Relativo a la zona (@ = la zona). |
| `records[].fqdn` | string |  |
| `records[].content` | string |  |
| `records[].priority` | integer | Prioridad de MX y SRV (null en los demás tipos). |
| `records[].purpose` | string | Uno de `mx`, `spf`, `dkim`, `dmarc`, `verification`, `autoconfig`, `return_path`, `tracking`. |
| `records[].provider` | string |  |
| `records[].action` | string | Uno de `create`, `update`, `keep`, `manual`. |
| `records[].record_id` | string |  |
| `records[].current` | string |  |
| `delete` | array de object |  |
| `delete[].record_id` | string |  |
| `delete[].type` | string |  |
| `delete[].name` | string | Nombre. |
| `delete[].content` | string |  |
| `delete[].priority` | integer | Prioridad de MX y SRV (null en los demás tipos). |
| `delete[].reason` | string | Motivo del cambio. Uno de `replaced_mx`, `duplicate_spf`, `replaced_dmarc`, `replaced_record`. |
| `batch` | object o null | Cuerpo para POST …/records/batch. |
| `issues` | array de object |  |
| `issues[].severity` | string | Uno de `error`, `warning`. |
| `issues[].code` | string |  |
| `issues[].field` | string |  |
| `issues[].message` | string |  |
| `issues[].params` | mapa de string |  |
| `spf` | object o null |  |
| `spf.value` | string |  |
| `spf.lookups` | integer | -1 si no se contaron. |
| `spf.error` | string |  |
| `dmarc` | string |  |

#### Errores

| Estado | Descripción |
|---|---|
| `400` | Petición mal formada (`bad_request`). |
| `404` | No existe o el token no lo ve (`not_found`). |
| `502` | `dns_unavailable`: el DNS público no respondió (zona externa). |
| `503` | La comprobación no está disponible ahora (zona externa), o `busy`: demasiadas comprobaciones en curso. |

#### Ejemplo

**curl**


```sh
curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/email/plan" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "providers": [
      {
        "id": "google"
      }
    ],
    "dmarc": {
      "policy": "none"
    }
  }'
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token de OPENWAF_TOKEN
const result = await owf.planEmailRecords({
  zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
  body: {
    providers: [
      {
        id: "google"
      }
    ],
    dmarc: {
      policy: "none"
    }
  }
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token de OPENWAF_TOKEN
result = owf.plan_email_records(
    zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
    body={
        "providers": [
            {
                "id": "google"
            }
        ],
        "dmarc": {
            "policy": "none"
        }
    },
)
```



```json
{
  "zone": "example.com",
  "dns_mode": "delegated",
  "applicable": false,
  "records": [
    {
      "type": "A",
      "name": "example.com",
      "fqdn": "example",
      "content": "203.0.113.10",
      "priority": 0,
      "purpose": "mx",
      "provider": "example",
      "action": "block",
      "record_id": "aa747ff3-b956-4f78-aa3e-6502aeccf60a",
      "current": "example"
    }
  ],
  "delete": [
    {
      "record_id": "aa747ff3-b956-4f78-aa3e-6502aeccf60a",
      "type": "A",
      "name": "example.com",
      "content": "203.0.113.10",
      "priority": 0,
      "reason": "Point www at the new origin"
    }
  ],
  "batch": {},
  "issues": [
    {
      "severity": "error",
      "code": "example",
      "field": "example",
      "message": "",
      "params": {
        "key": "example"
      }
    }
  ],
  "spf": {
    "value": "4f1c2e9a7b3d",
    "lookups": 0,
    "error": ""
  },
  "dmarc": "example"
}
```

## Comprobar los registros de correo

```http
GET /v1/orgs/{org}/zones/{zone}/email/check
```

Comprueba en resolvedores públicos los registros de correo de la zona, MX presentes y coherentes, un único SPF y sus búsquedas DNS (como mucho 10 con los include y redirect anidados), claves DKIM, DMARC, nombres de correo con proxy, conflictos CNAME y, en zonas delegadas, si el mundo ve los registros de la zona. Cada comprobación es `ok`, `warn`, `fail` o `info` con un `code` estable y sus `params`. El resultado se guarda 60 segundos.

**Rol necesario:** `readonly` o superior. `refresh=true` exige `tech`.

#### Parámetros

| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `org` | ruta | string (uuid) | Sí | Id de la organización. Cada ruta comprueba que es la del token (si no, `404`). |
| `zone` | ruta | string (uuid) | Sí | Id de la zona (consulta [Listar zonas](https://developers.openwaf.dev/es/api/zones/#list-zones)). |
| `selectors` | consulta | string | No | Selectores DKIM adicionales separados por comas (máx. 10). |
| `refresh` | consulta | boolean | No | Comprobar de nuevo sin usar el resultado guardado (rol tech). |

#### Respuesta

- `200`: Devuelve [EmailReport](https://developers.openwaf.dev/es/api/schemas/#email-report).

| Campo | Tipo | Descripción |
|---|---|---|
| `zone` | string |  |
| `dns_mode` | string | Uno de `delegated`, `external`. |
| `checked_at` | string (date-time) | Última comprobación. |
| `summary` | object |  |
| `summary.status` | string | Uno de `ok`, `warn`, `fail`. |
| `summary.ok` | integer |  |
| `summary.warn` | integer |  |
| `summary.fail` | integer |  |
| `summary.info` | integer |  |
| `providers` | array de string |  |
| `checks` | array de object |  |
| `checks[].id` | string | Id. Uno de `mx`, `spf_record`, `spf_lookups`, `spf_policy`, `dkim`, `dmarc`, `proxied`, `conflicts`, `world`. |
| `checks[].status` | string | Uno de `ok`, `warn`, `fail`, `info`. |
| `checks[].code` | string |  |
| `checks[].subject` | string |  |
| `checks[].message` | string |  |
| `checks[].params` | mapa de string |  |
| `public` | object |  |
| `public.mx` | array de string |  |
| `public.spf` | array de string |  |
| `public.dmarc` | array de string |  |
| `public.dkim` | array de object |  |
| `spf` | object o null |  |
| `spf.lookups` | integer |  |
| `spf.void_lookups` | integer |  |
| `spf.truncated` | boolean | El recuento se detuvo en 30 búsquedas. |
| `spf.all` | string |  |
| `spf.tree` | array de object |  |
| `spf.problems` | array de object |  |

#### Errores

| Estado | Descripción |
|---|---|
| `403` | `refresh=1` necesita el rol tech. |
| `404` | No existe o el token no lo ve (`not_found`). |
| `503` | La comprobación no está disponible ahora, o `busy`: demasiadas comprobaciones en curso. |

#### Ejemplo

**curl**


```sh
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/email/check" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
```

**TypeScript**


```ts
import { OpenWAF } from "./openwaf";

const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token de OPENWAF_TOKEN
const result = await owf.checkEmail({
  zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"
});
```

**Python**


```py
from openwaf import OpenWAF

owf = OpenWAF(base_url="https://dash.openwaf.dev")  # token de OPENWAF_TOKEN
result = owf.check_email(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d")
```



```json
{
  "zone": "example.com",
  "dns_mode": "delegated",
  "checked_at": "2026-10-27T09:30:00Z",
  "summary": {
    "status": "ok",
    "ok": 0,
    "warn": 0,
    "fail": 0,
    "info": 0
  },
  "providers": [
    "example"
  ],
  "checks": [
    {
      "id": "mx",
      "status": "ok",
      "code": "example",
      "subject": "example",
      "message": "",
      "params": {
        "key": "example"
      }
    }
  ],
  "public": {
    "mx": [
      "example"
    ],
    "spf": [
      "example"
    ],
    "dmarc": [
      "example"
    ],
    "dkim": [
      {
        "selector": "google",
        "name": "example.com",
        "value": "4f1c2e9a7b3d",
        "cname": "example"
      }
    ]
  },
  "spf": {
    "lookups": 0,
    "void_lookups": 0,
    "truncated": false,
    "all": "example",
    "tree": [
      {}
    ],
    "problems": []
  }
}
```
