Capítulo 14 Clientes, groupware & CLI Admin: #pApiAuth

Referencia completa de campos de API & CLI: los 12 tipos de objeto, endpoints REST & POST /api/v1/send

Esta referencia documenta cada propiedad JSON, tipo de datos, valor enum, ajuste predeterminado y rol RBAC mínimo de los 12 objetos de gestión (/api/admin/cli/objects/{type} & nextxch-cli), así como la entrega de correo transaccional (POST /api/v1/send). Utilice el cuadro de filtro instantáneo situado sobre la tabla para buscar cualquier propiedad, como dkimSigningMode, tlsMode, allowedCidrs o warmingEnabled.

Consejo operativo y nota de arquitectura

Introspección en vivo en su terminal o mediante HTTP: cada nextxch expone este esquema completo en JSON legible por máquina en GET /api/admin/cli/schema (con la cabecera SHA-256 X-Nextxch-Schema-Version). En su terminal, ejecute en cualquier momento nextxch-cli describe <Type> (p. ej., nextxch-cli describe Domain o nextxch-cli describe SmtpQueue).

Entrega transaccional

TransactionalSendRequest (POST /api/v1/send)

Clave: Idempotency-Key RBAC: User+
13 propiedades

Endpoint HTTP JSON de alto rendimiento para la entrega desde aplicaciones web, ERP, CRM y códigos 2FA, con priorización por categorías, calentamiento de IP, comprobación previa de supresión e idempotencia de 24 h.

POST /api/v1/send Pone en cola y firma el correo transaccional (UTF-8 Multipart/Alternative + archivos adjuntos)
POST /api/v1/unsubscribe/{token} Endpoint público de baja con un clic según RFC 8058 (token firmado con DKIM)
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ from string OPCIONAL
Predeterminado: Key senderAddress
Dirección del remitente. Para las claves Role=User, se usa automáticamente la senderAddress vinculada a la clave.
├─ to string[] OBLIGATORIO
Array de direcciones de correo de los destinatarios principales (se requiere al menos 1).
├─ cc string[] OPCIONAL
Predeterminado: []
Destinatarios en copia opcionales.
├─ bcc string[] OPCIONAL
Predeterminado: []
Destinatarios opcionales en copia oculta.
├─ replyTo string OPCIONAL
Dirección Reply-To opcional.
├─ subject string OBLIGATORIO
Línea de asunto del correo (UTF-8).
├─ textBody string OPCIONAL
Cuerpo de texto sin formato (text/plain). Se requiere al menos uno de textBody o htmlBody.
├─ htmlBody string OPCIONAL
Cuerpo HTML (text/html). Proporciónelo junto con textBody para una entregabilidad de 10/10.
├─ category string OPCIONAL
Predeterminado: transactional
Selecciona la prioridad de cola, la omisión del calentamiento de IP y el grupo de cuota del proveedor del destinatario.
Enum:"critical""transactional""notification""marketing"
├─ overflowMode string OPCIONAL
Predeterminado: DeferQueue
Comportamiento cuando se alcanza la cuota horaria del proveedor de destino (distribución gradual en la cola frente a HTTP 429 + Retry-After).
Enum:"DeferQueue""Reject429"
├─ idempotencyKey string OPCIONAL
Predeterminado: Header Idempotency-Key
Clave de deduplicación determinista (p. ej., ID de factura) que evita envíos duplicados en un plazo de 24 horas.
├─ headers Record<string, string> OPCIONAL
Predeterminado: {}
Diccionario de encabezados X personalizados según RFC 5322.
├─ attachments SendAttachmentDto[] OPCIONAL
Predeterminado: []
Array de { fileName: string, contentType: string, base64Content: string }.
{
  "from": "Abrechnung <rechnung@firma.de>",
  "to": ["einkauf@kunde.de"],
  "cc": ["buchhaltung@firma.de"],
  "subject": "Ihre elektronische Rechnung RE-2026-90412",
  "category": "transactional",
  "overflowMode": "DeferQueue",
  "textBody": "Guten Tag,\nanbei erhalten Sie Ihre Rechnung RE-2026-90412.",
  "htmlBody": "<p>Guten Tag,</p><p>anbei erhalten Sie Ihre Rechnung <strong>RE-2026-90412</strong>.</p>",
  "headers": {
    "X-ERP-Document-Id": "RE-2026-90412"
  },
  "attachments": [
    {
      "fileName": "RE-2026-90412.pdf",
      "contentType": "application/pdf",
      "base64Content": "JVBERi0xLjcKJcfs..."
    }
  ]
}
# 1) Kleinstprivilegierten Sende-Key (Role=User) für rechnung@firma.de erstellen:
nextxch-cli create ApiKey \
  --set label="ERP Rechnungsversand" \
  --set role=User \
  --set senderAddress=rechnung@firma.de \
  --set defaultCategory=transactional \
  --set allowedCidrs=10.20.0.0/16

# 2) Risikofreier Trockentest gegen die integrierte Sandbox-Sink-Domain:
# ok@sink.nextxch.local | bounce@sink.nextxch.local | defer@sink.nextxch.local
curl -fsS -X POST "https://mail.firma.de/api/v1/send" \
  -H "Authorization: Bearer smk_01968f2a_..." \
  -H "Idempotency-Key: inv-2026-90412" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "rechnung@firma.de",
    "to": ["einkauf@kunde.de"],
    "subject": "Ihre Rechnung RE-2026-90412",
    "category": "transactional",
    "textBody": "Guten Tag, anbei Ihre Rechnung RE-2026-90412.",
    "htmlBody": "<p>Guten Tag, anbei Ihre Rechnung <b>RE-2026-90412</b>.</p>"
  }'
using System.Net.Http.Headers;
using System.Net.Http.Json;

using var http = new HttpClient { BaseAddress = new Uri("https://mail.firma.de") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "smk_01968f2a_...");
http.DefaultRequestHeaders.Add("Idempotency-Key", "inv-2026-90412");

var response = await http.PostAsJsonAsync("/api/v1/send", new
{
    from = "rechnung@firma.de",
    to = new[] { "einkauf@kunde.de" },
    subject = "Ihre Rechnung RE-2026-90412",
    category = "transactional",
    textBody = "Guten Tag, anbei Ihre Rechnung RE-2026-90412.",
    htmlBody = "<p>Guten Tag, anbei Ihre Rechnung <strong>RE-2026-90412</strong>.</p>"
});
response.EnsureSuccessStatusCode();
Objeto de colección (CRUD)

IntegrationKey (API v1)

Clave: id RBAC: TenantAdmin+
9 propiedades

Clave de integración con ámbitos para /api/v1. Una clave actúa en nombre de un usuario o, como integración de inquilino, en nombre del usuario indicado en el encabezado X-Nextxch-Act-As. Sin el ámbito, la API responde 403; un usuario de otro inquilino da 404. Credenciales de cliente OAuth 2.0 opcionales (token válido durante 1 hora).

POST /api/admin/integrations/keys Crear una clave (admin, se muestra una vez)
PUT /api/admin/integrations/keys/{id} Cambiar ámbitos
DELETE /api/admin/integrations/keys/{id} Revocar una clave (y sus tokens OAuth)
POST /api/v1/oauth/token OAuth 2.0 client_credentials: ID de clave + clave para obtener un token de acceso
GET /api/v1/me Clave, ámbitos y usuario que actúa
GET /api/v1/openapi.json Documento OpenAPI 3.1 de la instalación
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ label string OBLIGATORIO
Etiqueta, p. ej., CRM o ERP.
├─ mode string OBLIGATORIO
Predeterminado: user
user: actúa siempre en nombre de userEmail. tenant: actúa en nombre de X-Nextxch-Act-As (solo usuarios del propio inquilino).
Enum:"user""tenant"
├─ userEmail string OPCIONAL
Usuario para mode=user.
├─ scopes string[] OBLIGATORIO
Permisos; .write incluye .read. crypto.read y crypto.manage (S/MIME, PGP) están reservados.
Enum:"calendar.read""calendar.write""rooms.read""booking.read""booking.write""contacts.read""contacts.write""directory.read""sharing.manage""webhooks.manage""mail.send"
├─ domainId guid OPCIONAL
Integración del inquilino limitada a los usuarios de este dominio.
├─ allowedCidrs string[] OPCIONAL
Predeterminado: []
Redes IP permitidas.
├─ expiresInDays integer OPCIONAL
Caducidad en días (vacío: ilimitado).
├─ senderAddress string OPCIONAL
Remitente para mail.send (POST /api/v1/send).
├─ apiKey string SOLO LECTURA
smk_<id>_<secret>, solo en la respuesta de creación; solo se almacena el hash.
{
  "label": "CRM",
  "mode": "tenant",
  "scopes": ["calendar.write", "rooms.read", "contacts.write", "webhooks.manage"],
  "allowedCidrs": ["203.0.113.0/24"],
  "expiresInDays": 365
}
# Integrations-Schlüssel: Admin > API & Integrationen > Neuer Schlüssel
# Danach z. B. ein Access-Token per OAuth 2.0 Client Credentials holen:
curl -fsS -X POST "https://mail.firma.de/api/v1/oauth/token" \
  -d grant_type=client_credentials \
  -d client_id=0196c3a2-5f1e-7c11-9a20-1c2d3e4f5a6b \
  -d client_secret=smk_0196c3a25f1e7c119a201c2d3e4f5a6b_... \
  -d scope="calendar.read rooms.read"
curl -fsS "https://mail.firma.de/api/v1/me" \
  -H "Authorization: Bearer smk_0196c3a2..." \
  -H "X-Nextxch-Act-As: anna@firma.de"
using var http = new HttpClient { BaseAddress = new Uri("https://mail.firma.de") };
http.DefaultRequestHeaders.Authorization = new("Bearer", "smk_0196c3a2...");
http.DefaultRequestHeaders.Add("X-Nextxch-Act-As", "anna@firma.de");
var me = await http.GetFromJsonAsync<JsonElement>("/api/v1/me");
Objeto de colección (CRUD)

CalendarEvent (API v1)

Clave: hrefName RBAC: User+
12 propiedades

Eventos con series y excepciones, asistentes, salas, invitaciones, respuestas, disponible/ocupado, calendarios compartidos y globales y enlaces de invitación. Ámbitos calendar.read y calendar.write. Horas en UTC (ISO-8601), zonas horarias como nombres IANA.

GET /api/v1/calendars Calendarios propios, compartidos, globales y de recursos
GET /api/v1/calendar/events?from&to&calendarId&limit&cursor Repeticiones en un intervalo (serie expandida)
POST /api/v1/calendar/events Crear un evento (invitar a personas y salas)
GET /api/v1/calendar/events/{hrefName} Evento con series y excepciones
PUT /api/v1/calendar/events/{hrefName} Modificación; scope=occurrence|following para partes de una serie
DELETE /api/v1/calendar/events/{hrefName} Eliminar, opcionalmente con cancelaciones (notify=true)
POST /api/v1/calendar/events/{hrefName}/respond Responder a una invitación
POST /api/v1/calendar/events/{hrefName}/invite-link Enlace de invitación firmado
GET /api/v1/calendar/events/{hrefName}/ics iCalendar sin procesar
POST /api/v1/calendar/freebusy Disponible/ocupado de personas y salas
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ title string OBLIGATORIO
Título.
├─ startUtc datetime OBLIGATORIO
Inicio en UTC.
├─ endUtc datetime OBLIGATORIO
Fin en UTC.
├─ timeZone string OPCIONAL
Predeterminado: Europe/Berlin
Zona horaria IANA del evento (serie, visualización).
├─ allDay boolean OPCIONAL
Predeterminado: false
Todo el día.
├─ attendees Attendee[] OPCIONAL
Predeterminado: []
{ email, name, role }; las salas y los recursos aceptan o rechazan según sus reglas.
├─ sendInvitations boolean OPCIONAL
Predeterminado: false
Enviar invitaciones iMIP.
├─ recurrence Recurrencia OPCIONAL
{ freq, interval, byDay, byMonthDay, count, until }.
├─ showAs string OPCIONAL
Predeterminado: busy
Mostrar como.
Enum:"free""tentative""busy""oof""workingElsewhere"
├─ isPrivate boolean OPCIONAL
Predeterminado: false
Privado (detalles solo para el propietario).
├─ calendarId string OPCIONAL
Calendario de destino ownerId:slug con permisos de escritura.
├─ teamsMeeting boolean OPCIONAL
Crea una reunión de Microsoft Teams.
{
  "title": "Projektstart",
  "startUtc": "2026-12-03T09:00:00Z",
  "endUtc": "2026-12-03T10:00:00Z",
  "timeZone": "Europe/Berlin",
  "attendees": [
    { "email": "bernd@firma.de" },
    { "email": "raum-1@firma.de" }
  ],
  "sendInvitations": true
}
# Benötigte Scopes: calendar.read (lesen), calendar.write (schreiben)
# Einen Raum buchen: Raumadresse als Teilnehmer eintragen
curl -fsS -X POST "https://mail.firma.de/api/v1/calendar/events" \
  -H "Authorization: Bearer smk_0196c3a2..." \
  -H "X-Nextxch-Act-As: anna@firma.de" \
  -H "Content-Type: application/json" \
  -d '{"title":"Projektstart","startUtc":"2026-12-03T09:00:00Z","endUtc":"2026-12-03T10:00:00Z","timeZone":"Europe/Berlin","attendees":[{"email":"raum-1@firma.de"}]}'
var res = await http.PostAsJsonAsync("/api/v1/calendar/events", new
{
    title = "Projektstart",
    startUtc = DateTimeOffset.Parse("2026-12-03T09:00:00Z"),
    endUtc = DateTimeOffset.Parse("2026-12-03T10:00:00Z"),
    timeZone = "Europe/Berlin",
    attendees = new[] { new { email = "raum-1@firma.de" } }
});
string href = (await res.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("data").GetProperty("hrefName").GetString()!;
Objeto de colección (CRUD)

Rooms & Booking (API v1)

Clave: id RBAC: User+
6 propiedades

Salas y recursos con disponibilidad (rooms.read) y páginas de reservas al estilo de Calendly (booking.read, booking.write): página de reservas, tipos de eventos, franjas libres, lectura y cancelación de reservas. Una sala se reserva mediante un evento con la sala como asistente.

GET /api/v1/rooms?startUtc&endUtc&category Salas y recursos, con disponibilidad para el intervalo
GET /api/v1/rooms/{email}/availability Horas ocupadas de una sala
GET /api/v1/booking/profile Página de reservas con horarios y tipos de evento
GET /api/v1/booking/event-types Tipos de eventos
POST /api/v1/booking/event-types Crear un tipo de evento
GET /api/v1/booking/event-types/{id}/slots?from&to Horarios de inicio libres (máx. 42 días)
GET /api/v1/booking/bookings?status&from&to Reservas
POST /api/v1/booking/bookings/{id}/cancel Cancelar una reserva (se informa al invitado)
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ slots[].startUtc datetime SOLO LECTURA
Inicio de la franja libre en UTC.
├─ slots[].endUtc datetime SOLO LECTURA
Fin (inicio más duración).
├─ slots[].staff object[] SOLO LECTURA
Personal libre para tipos de evento de equipo.
├─ status string SOLO LECTURA
Estado de la reserva.
Enum:"confirmed""cancelled"
├─ guest object SOLO LECTURA
{ name, email, phone, timeZone }.
├─ free boolean SOLO LECTURA
Sala libre en la franja.
{
  "ok": true,
  "data": {
    "eventTypeId": "beratung-3f2a",
    "durationMinutes": 30,
    "timeZone": "Europe/Berlin",
    "slots": [
      { "startUtc": "2026-12-04T08:00:00Z", "endUtc": "2026-12-04T08:30:00Z", "staff": [] }
    ]
  }
}
# Benötigte Scopes: rooms.read, booking.read, booking.write
curl -fsS "https://mail.firma.de/api/v1/rooms?startUtc=2026-12-03T09:00:00Z&endUtc=2026-12-03T10:00:00Z" \
  -H "Authorization: Bearer smk_0196c3a2..."
var slots = await http.GetFromJsonAsync<JsonElement>("/api/v1/booking/event-types/beratung/slots");
foreach (var s in slots.GetProperty("data").GetProperty("slots").EnumerateArray())
    Console.WriteLine(s.GetProperty("startUtc").GetString());
Objeto de colección (CRUD)

Contact, Directory & Sharing (API v1)

Clave: hrefName RBAC: User+
9 propiedades

Contactos personales con todos los campos y foto (contacts.read, contacts.write, If-Match para la protección frente a conflictos), lista global de direcciones (directory.read) y usos compartidos y delegación (sharing.manage, delegación = permiso de buzón con sendAs o sendOnBehalf).

GET /api/v1/contacts?q Buscar contactos
POST /api/v1/contacts Crear un contacto
PUT /api/v1/contacts/{hrefName} Modificar un contacto (los campos null se mantienen)
DELETE /api/v1/contacts/{hrefName} Eliminar un contacto
GET /api/v1/directory?q&type Lista global de direcciones
GET /api/v1/sharing/grants Permisos propios
PUT /api/v1/sharing/grants Establecer o quitar una concesión
GET /api/v1/sharing/shared-with-me Compartido conmigo
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ givenName string OPCIONAL
Nombre de pila.
├─ familyName string OPCIONAL
Apellido.
├─ organization string OPCIONAL
Empresa.
├─ emails TypedValue[] OPCIONAL
Predeterminado: []
{ type: work|home|other, value }.
├─ phones TypedValue[] OPCIONAL
Predeterminado: []
{ type: cell|work|home|fax, value }.
├─ addresses Address[] OPCIONAL
Predeterminado: []
{ type, street, city, region, postalCode, country }.
├─ birthday string OPCIONAL
yyyy-MM-dd o --MM-dd.
├─ photo string SOLO ESCRITURA (SECRETO)
data:image/jpeg;base64,... (máx. 512 KB), vacío elimina la imagen.
├─ permissions string[] OPCIONAL
Derechos de una concesión (PUT /sharing/grants).
Enum:"freeBusy""read""write""delete""sendOnBehalf""sendAs""manage"
{
  "givenName": "Clara",
  "familyName": "Kunde",
  "organization": "Kunde AG",
  "emails": [{ "type": "work", "value": "clara@kunde.example" }],
  "phones": [{ "type": "cell", "value": "+49 170 1234567" }]
}
# Benötigte Scopes: contacts.read, contacts.write, directory.read, sharing.manage
curl -fsS -X POST "https://mail.firma.de/api/v1/contacts" \
  -H "Authorization: Bearer smk_0196c3a2..." \
  -H "Content-Type: application/json" \
  -d '{"givenName":"Clara","familyName":"Kunde","emails":[{"type":"work","value":"clara@kunde.example"}]}'
await http.PutAsJsonAsync("/api/v1/sharing/grants", new
{
    kind = "calendar", granteeType = "user", grantee = "bernd@firma.de", permissions = new[] { "read" }
});
Objeto de colección (CRUD)

Webhook (API v1)

Clave: id RBAC: User+
4 propiedades

Eventos como POST JSON firmado: calendar.event.created, calendar.event.updated, calendar.event.deleted, calendar.invitation.replied, booking.created, booking.rescheduled, booking.cancelled. Firma X-Nextxch-Signature: v1=hex(HMAC-SHA256(secret, X-Nextxch-Timestamp + "." + body)). Reintentos tras 30 s, 2 min, 10 min, 1 h y 6 h.

GET /api/v1/webhooks Webhooks del inquilino
POST /api/v1/webhooks Registrar un webhook (secreto solo en la respuesta)
DELETE /api/v1/webhooks/{id} Eliminar un webhook
GET /api/v1/webhooks/{id}/deliveries Entregas recientes
POST /api/v1/webhooks/{id}/test Enviar un evento de prueba
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ url string OBLIGATORIO
Destino HTTPS (las direcciones internas están bloqueadas).
├─ events string[] OBLIGATORIO
Eventos; la clave necesita los ámbitos .read correspondientes.
Enum:"calendar.event.created""calendar.event.updated""calendar.event.deleted""calendar.invitation.replied""booking.created""booking.rescheduled""booking.cancelled""*"
├─ secret string SOLO LECTURA
whsec_..., solo al crearlo; se almacena cifrado.
├─ id (Ereignis) string SOLO LECTURA
evt_... estable por cambio, para la deduplicación.
{
  "id": "evt_5c1f0e9a2b7d4c3e8f601a2b",
  "type": "calendar.invitation.replied",
  "createdAtUtc": "2026-12-01T10:15:02Z",
  "tenantId": "0196c3a2-0000-7000-8000-000000000001",
  "data": {
    "calendarId": "0196c3a25f1e7c11:default",
    "hrefName": "0196c3a2@nextxch.ics",
    "title": "Projektstart",
    "startUtc": "2026-12-03T09:00:00Z",
    "attendee": { "email": "bernd@firma.de", "partStat": "ACCEPTED" }
  }
}
# Signatur prüfen (Bash):
# expected="v1=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)"
curl -fsS -X POST "https://mail.firma.de/api/v1/webhooks" \
  -H "Authorization: Bearer smk_0196c3a2..." \
  -H "Content-Type: application/json" \
  -d '{"url":"https://crm.firma.de/hooks/nextxch","events":["calendar.event.created","booking.created"]}'
// ASP.NET Core receiver
app.MapPost("/hooks/nextxch", async (HttpRequest req) =>
{
    using var ms = new MemoryStream();
    await req.Body.CopyToAsync(ms);
    string ts = req.Headers["X-Nextxch-Timestamp"]!;
    byte[] mac = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), [.. Encoding.ASCII.GetBytes(ts + "."), .. ms.ToArray()]);
    return req.Headers["X-Nextxch-Signature"] == "v1=" + Convert.ToHexStringLower(mac) ? Results.Ok() : Results.Unauthorized();
});
Objeto de colección (CRUD)

Tenant

Clave: slug RBAC: ServerAdmin+
11 propiedades

Límite organizativo multiinquilino que aísla dominios, entidades de seguridad, cuotas de almacenamiento (GiB) y la retención de cumplimiento GoBD WORM predeterminada.

GET /api/admin/cli/objects/Tenant Enumera todos los inquilinos
GET /api/admin/cli/objects/Tenant/{slugOrId} Obtiene el inquilino por slug o UUID
POST /api/admin/cli/objects/Tenant Crea un nuevo inquilino
PATCH /api/admin/cli/objects/Tenant/{slugOrId} Actualiza las cuotas del inquilino en el momento
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ id guid SOLO LECTURA
Predeterminado: UUIDv7
Identificador interno inmutable UUIDv7.
├─ slug string PRIMARY KEY
Clave de inquilino única y apta para URL (p. ej., "default", "acme").
├─ name string OBLIGATORIO
Nombre visible legible de la organización.
├─ isActive boolean OPCIONAL
Predeterminado: true
Activar o suspender el inquilino.
├─ maxDomains integer OPCIONAL
Predeterminado: 100
Número máximo de dominios permitidos en este inquilino.
├─ maxPrincipals integer OPCIONAL
Predeterminado: 10000
Máximo de cuentas y listas de distribución.
├─ quotaGb integer OPCIONAL
Predeterminado: 100
Límite de almacenamiento del inquilino en GiB.
├─ outboundDailyLimit integer OPCIONAL
Predeterminado: 25000
Máximo de correos salientes por día en todo el inquilino.
├─ defaultLockMode string OPCIONAL
Predeterminado: Normal
Modo de bloqueo GoBD WORM predeterminado para los dominios nuevos.
Enum:"Normal""Governance""Compliance""LegalHold"
├─ defaultRetentionDays integer OPCIONAL
Predeterminado: 0
Periodo de retención obligatorio predeterminado en días (3650 = 10 años GoBD).
├─ createdAtUtc datetime SOLO LECTURA
Marca de tiempo de creación en UTC.
{
  "slug": "acme",
  "name": "ACME Logistik GmbH",
  "isActive": true,
  "maxDomains": 50,
  "maxPrincipals": 2500,
  "quotaGb": 250,
  "outboundDailyLimit": 25000,
  "defaultLockMode": "Compliance",
  "defaultRetentionDays": 3650
}
# Mandant anlegen:
nextxch-cli create Tenant \
  --set slug=acme \
  --set name="ACME Logistik GmbH" \
  --set quotaGb=250 \
  --set defaultLockMode=Compliance \
  --set defaultRetentionDays=3650

# Mandant abfragen & Kontingent erhöhen:
nextxch-cli get Tenant acme
nextxch-cli update Tenant acme --set quotaGb=500
curl -fsS -X POST "https://mail.firma.de/api/admin/cli/objects/Tenant" \
  -H "Authorization: Bearer smk_..." \
  -H "Content-Type: application/json" \
  -d '{"slug":"acme","name":"ACME Logistik GmbH","quotaGb":250,"defaultLockMode":"Compliance","defaultRetentionDays":3650}'
var payload = new
{
    slug = "acme",
    name = "ACME Logistik GmbH",
    quotaGb = 250,
    defaultLockMode = "Compliance",
    defaultRetentionDays = 3650
};
using var res = await http.PostAsJsonAsync("/api/admin/cli/objects/Tenant", payload);
Objeto de colección (CRUD)

Domain

Clave: name RBAC: TenantAdmin+
19 propiedades

Dominio de correo alojado con generación automática y paralela de claves Dual-DKIM (RSA-2048 + Ed25519), política de retención GoBD WORM, direcciones con sufijo (plus addressing) y límites de tasa.

GET /api/admin/cli/objects/Domain Enumera todos los dominios
GET /api/admin/cli/objects/Domain/{name} Obtiene la configuración del dominio & los selectores DKIM
POST /api/admin/cli/objects/Domain Crea el dominio & genera claves DKIM RSA-2048 + Ed25519
PATCH /api/admin/cli/objects/Domain/{name} Actualiza el modo DKIM, las cuotas o la retención en 0 ms
GET /api/admin/cli/dns/{name}?live=true Verifica registros DNS en vivo o exporta una zona BIND según RFC 1035
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ id guid SOLO LECTURA
Predeterminado: UUIDv7
UUIDv7 interno del dominio.
├─ name string PRIMARY KEY
Nombre de dominio DNS (p. ej., "muster-logistik.de").
├─ tenant string OPCIONAL
Predeterminado: default
Slug o GUID del inquilino propietario.
├─ isActive boolean OPCIONAL
Predeterminado: true
Activar o pausar el dominio.
├─ postmasterAddress string OPCIONAL
Predeterminado: postmaster@<name>
Dirección de contacto postmaster según RFC 5321.
├─ dkimSigningMode string OPCIONAL
Predeterminado: Dual
Modo de firma DKIM activo (Dual firma con RSA-2048 y Ed25519).
Enum:"Rsa2048""Ed25519""Dual"
├─ dkimRsaSelector string OPCIONAL
Predeterminado: s2026
Selector DNS RSA-2048.
├─ dkimRsaDnsName string SOLO LECTURA
Predeterminado: s2026._domainkey.<name>
FQDN _domainkey RSA-2048 calculado.
├─ dkimEd25519Selector string OPCIONAL
Predeterminado: ed2026
Selector DNS Ed25519.
├─ dkimEd25519DnsName string SOLO LECTURA
Predeterminado: ed2026._domainkey.<name>
FQDN _domainkey de Ed25519 calculado.
├─ retentionLockMode string OPCIONAL
Predeterminado: Normal
Modo de bloqueo del archivo WORM GoBD para este dominio.
Enum:"Normal""Governance""Compliance""LegalHold"
├─ retentionDays integer OPCIONAL
Predeterminado: 0
Duración obligatoria de la retención en días (3650 = 10 años, GoBD).
├─ quotaGb integer OPCIONAL
Predeterminado: 50
Límite de almacenamiento del dominio en GiB.
├─ maxMessageSizeMb integer OPCIONAL
Predeterminado: 50
Tamaño máximo del mensaje en MiB.
├─ maxAccounts integer OPCIONAL
Predeterminado: 1000
Máximo de buzones en este dominio.
├─ outboundDailyLimit integer OPCIONAL
Predeterminado: 5000
Límite diario de envío saliente para este dominio.
├─ outboundHourlyLimit integer OPCIONAL
Predeterminado: 1000
Límite de envío saliente por hora para este dominio.
├─ usePlusAddressing boolean OPCIONAL
Predeterminado: true
Activa el direccionamiento plus de RFC 5233 (user+tag@domain).
├─ createdAtUtc datetime SOLO LECTURA
Marca de tiempo de creación en UTC.
{
  "name": "muster-logistik.de",
  "tenant": "default",
  "isActive": true,
  "postmasterAddress": "postmaster@muster-logistik.de",
  "dkimSigningMode": "Dual",
  "dkimRsaSelector": "s2026",
  "dkimEd25519Selector": "ed2026",
  "retentionLockMode": "Compliance",
  "retentionDays": 3650,
  "quotaGb": 100,
  "maxMessageSizeMb": 50,
  "maxAccounts": 500,
  "outboundDailyLimit": 10000,
  "outboundHourlyLimit": 2000,
  "usePlusAddressing": true
}
# Domain mit Dual-DKIM und 10 Jahren GoBD-Compliance anlegen:
nextxch-cli create Domain \
  --set name=muster-logistik.de \
  --set dkimSigningMode=Dual \
  --set retentionLockMode=Compliance \
  --set retentionDays=3650

# Fertige RFC-1035 BIND-Zonendatei exportieren & DNS live prüfen:
nextxch-cli dns muster-logistik.de --bind > muster-logistik.de.zone
nextxch-cli dns muster-logistik.de --live
curl -fsS -X PATCH "https://mail.firma.de/api/admin/cli/objects/Domain/muster-logistik.de" \
  -H "Authorization: Bearer smk_..." \
  -H "Content-Type: application/json" \
  -d '{"dkimSigningMode":"Dual","retentionLockMode":"Compliance","retentionDays":3650}'
using var res = await http.PatchAsJsonAsync(
    "/api/admin/cli/objects/Domain/muster-logistik.de",
    new { dkimSigningMode = "Dual", quotaGb = 200 });
res.EnsureSuccessStatusCode();
Objeto de colección (CRUD)

Account (Account/User & Account/List)

Clave: email RBAC: TenantAdmin+
18 propiedades

Representa un buzón de usuario (@type="User", con IMAP/JMAP/CalDAV/CardDAV/ActiveSync) o una lista de distribución (@type="List" con members[]).

GET /api/admin/cli/objects/Account?filter=role=TenantAdmin Enumera/filtra cuentas y listas de distribución
POST /api/admin/cli/objects/Account/User Crea un nuevo buzón de usuario con carpetas según RFC 6154
POST /api/admin/cli/objects/Account/List Crea una nueva lista de distribución
PATCH /api/admin/cli/objects/Account/{email} Actualiza cuota, reenvío, respuesta automática o contraseña
DELETE /api/admin/cli/objects/Account/{email} Elimina una cuenta de forma lógica (soft delete)
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ id guid SOLO LECTURA
Predeterminado: UUIDv7
UUIDv7 interno del principal.
├─ @type string OPCIONAL
Predeterminado: User
Variante de cuenta: buzón de usuario (User) o grupo de distribución (List).
Enum:"User""List"
├─ email string PRIMARY KEY
Dirección de correo principal del buzón o de la lista.
├─ displayName string OPCIONAL
Predeterminado: Lokalteil
Nombre visible que se muestra en el Webmail, la GAL y las cabeceras From.
├─ password string SOLO ESCRITURA (SECRETO)
Contraseña en texto plano al crear/actualizar (se convierte de inmediato en hash con Argon2id).
├─ role string OPCIONAL
Predeterminado: User
Rol RBAC administrativo.
Enum:"User""ComplianceAuditor""TenantAdmin""ServerAdmin"
├─ isActive boolean OPCIONAL
Predeterminado: true
Activar o suspender la cuenta.
├─ quotaMb integer OPCIONAL
Predeterminado: 5120
Cuota de almacenamiento del buzón en MiB (5120 = 5 GiB).
├─ usedBytes long SOLO LECTURA
Predeterminado: 0
Uso actual de almacenamiento del buzón en bytes.
├─ outboundDailyLimit integer OPCIONAL
Predeterminado: 500
Límite diario de mensajes salientes para esta cuenta.
├─ forwardAddress string OPCIONAL
Correo de destino de reenvío opcional compatible con SRS.
├─ forwardKeepOriginal boolean OPCIONAL
Predeterminado: true
Conservar una copia local en INBOX cuando el reenvío esté activo.
├─ vacationEnabled boolean OPCIONAL
Predeterminado: false
Activa la respuesta automática de fuera de la oficina.
├─ vacationSubject string OPCIONAL
Asunto de la respuesta automática de fuera de la oficina.
├─ vacationBody string SOLO ESCRITURA (SECRETO)
Texto del cuerpo de la respuesta automática de fuera de la oficina.
├─ twoFactorEnabled boolean SOLO LECTURA
Predeterminado: false
Indica si TOTP 2FA está activo.
├─ members string[] OPCIONAL
Predeterminado: []
Direcciones de correo de los miembros (para @type="List").
├─ createdAtUtc datetime SOLO LECTURA
Marca de tiempo de creación en UTC.
{
  "@type": "User",
  "email": "anna.weber@muster-logistik.de",
  "displayName": "Anna Weber (IT-Leitung)",
  "password": "SicheresStartPasswort!2026",
  "role": "TenantAdmin",
  "isActive": true,
  "quotaMb": 15360,
  "outboundDailyLimit": 1000,
  "forwardAddress": null,
  "forwardKeepOriginal": true,
  "vacationEnabled": false,
  "vacationSubject": "Abwesenheitsnotiz: Anna Weber",
  "vacationBody": "Ich bin ab Montag wieder erreichbar."
}
# Benutzerpostfach (15 GiB Quota) anlegen:
nextxch-cli create Account/User \
  --set email=anna.weber@muster-logistik.de \
  --set displayName="Anna Weber" \
  --set password="SicheresStartPasswort!2026" \
  --set quotaMb=15360

# Verteilerliste anlegen:
nextxch-cli create Account/List \
  --set email=team@muster-logistik.de \
  --set displayName="Disposition & Einkauf" \
  --set members=anna.weber@muster-logistik.de,dispo@muster-logistik.de
curl -fsS -X POST "https://mail.firma.de/api/admin/cli/objects/Account/User" \
  -H "Authorization: Bearer smk_..." \
  -H "Content-Type: application/json" \
  -d '{"email":"anna.weber@muster-logistik.de","displayName":"Anna Weber","password":"SicheresStartPasswort!2026","quotaMb":15360}'
await http.PostAsJsonAsync("/api/admin/cli/objects/Account/User", new
{
    email = "anna.weber@muster-logistik.de",
    displayName = "Anna Weber",
    password = "SicheresStartPasswort!2026",
    quotaMb = 15360
});
Objeto de colección (CRUD)

Alias

Clave: address RBAC: TenantAdmin+
7 propiedades

Alias de correo secundario o alias de privacidad enmascarado desechable, dirigido a un buzón Account principal o a una lista de distribución.

GET /api/admin/cli/objects/Alias Enumera todos los alias y correos enmascarados
POST /api/admin/cli/objects/Alias Crea un nuevo alias dirigido a un buzón de destino
PATCH /api/admin/cli/objects/Alias/{address} Activa/pausa un alias o actualiza su destino
DELETE /api/admin/cli/objects/Alias/{address} Elimina el alias
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ address string PRIMARY KEY
La dirección de correo del alias.
├─ target string OBLIGATORIO
Dirección de correo de la cuenta principal de destino.
├─ principalId guid SOLO LECTURA
UUID resuelto del principal de destino.
├─ description string OPCIONAL
Predeterminado: ""
Nota de finalidad o etiqueta de servicio externo (también se muestra en el editor de Webmail).
├─ isMasked boolean OPCIONAL
Predeterminado: false
Marca el alias como alias de privacidad enmascarado.
├─ isEnabled boolean OPCIONAL
Predeterminado: true
Si es false, el correo a este alias se rechaza sin liberar la dirección.
├─ createdAtUtc datetime SOLO LECTURA
Marca de tiempo de creación en UTC.
{
  "address": "shop-8f2k@muster-logistik.de",
  "target": "anna.weber@muster-logistik.de",
  "description": "Fachbuchhandlung / Online-Bestellung",
  "isMasked": true,
  "isEnabled": true
}
# Masked-Email-Alias anlegen:
nextxch-cli create Alias \
  --set address=shop-8f2k@muster-logistik.de \
  --set target=anna.weber@muster-logistik.de \
  --set description="Fachbuchhandlung" \
  --set isMasked=true

# Alias bei Spam-Aufkommen mit 1 Befehl stummschalten:
nextxch-cli update Alias shop-8f2k@muster-logistik.de --set isEnabled=false
curl -fsS -X POST "https://mail.firma.de/api/admin/cli/objects/Alias" \
  -H "Authorization: Bearer smk_..." \
  -H "Content-Type: application/json" \
  -d '{"address":"vertrieb@muster-logistik.de","target":"anna.weber@muster-logistik.de","description":"Vertrieb & Angebote"}'
await http.PostAsJsonAsync("/api/admin/cli/objects/Alias", new
{
    address = "vertrieb@muster-logistik.de",
    target = "anna.weber@muster-logistik.de",
    description = "Vertrieb & Angebote",
    isEnabled = true
});
Objeto de colección (CRUD)

ApiKey

Clave: id RBAC: TenantAdmin+
11 propiedades

Clave de API con ámbito (Scoped API Key), inmutable y restringida por RBAC y CIDR (smk_<id>_<secret>) para /api/v1/send, SCIM 2.0 o nextxch-cli.

GET /api/admin/cli/objects/ApiKey Enumera las claves de API activas (sin el secreto)
POST /api/admin/cli/objects/ApiKey Emite una nueva clave de API y devuelve apiKey (smk_...) una sola vez
DELETE /api/admin/cli/objects/ApiKey/{id} Revoca la clave de API de inmediato
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ id guid SOLO LECTURA
Predeterminado: UUIDv7
UUID de clave único (se usa para revocar mediante DELETE).
├─ apiKey string SOLO CREACIÓN
Predeterminado: smk_<id>_<secret>
El token Bearer completo se devuelve SOLO una vez, al crearlo.
├─ label string OBLIGATORIO
Etiqueta legible (p. ej., "Ansible GitOps" o "Shopware 6").
├─ role string OPCIONAL
Predeterminado: TenantAdmin
Ámbito de privilegios (User está estrictamente limitado a POST /api/v1/send).
Enum:"User""ComplianceAuditor""TenantAdmin""ServerAdmin"
├─ senderAddress string OPCIONAL
Dirección de remitente autorizada (obligatoria cuando role="User").
├─ defaultCategory string OPCIONAL
Predeterminado: transactional
Categoría de envío predeterminada para esta clave.
Enum:"critical""transactional""notification""marketing"
├─ allowedCidrs string[] OPCIONAL
Predeterminado: [] (Alle IPs)
Redes CIDR IPv4/IPv6 de origen permitidas.
├─ expiresInDays integer SOLO ESCRITURA (SECRETO)
Predeterminado: null (Kein Ablauf)
Caducidad automática opcional en días desde la creación.
├─ expiresAtUtc datetime SOLO LECTURA
Marca de tiempo de caducidad UTC calculada.
├─ outboundDailyLimit integer OPCIONAL
Predeterminado: 10000
Límite diario de envío saliente para esta clave.
├─ isActive boolean SOLO LECTURA
Predeterminado: true
Estado activo.
{
  "label": "Shopware 6 ERP Rechnungsversand",
  "role": "User",
  "senderAddress": "rechnung@muster-logistik.de",
  "defaultCategory": "transactional",
  "allowedCidrs": ["10.20.10.15/32", "2a01:4f8:120:44aa::/64"],
  "expiresInDays": 180,
  "outboundDailyLimit": 10000
}
# CIDR-geschützten Key für Ansible / nextxch-cli erstellen:
nextxch-cli create ApiKey \
  --set label="GitLab CI / Ansible" \
  --set role=TenantAdmin \
  --set allowedCidrs=10.20.0.0/16 \
  --set expiresInDays=90

# Schlüssel widerrufen:
nextxch-cli delete ApiKey 0195f2a0-8b14-7000-9a10-001122334455
curl -fsS -X POST "https://mail.firma.de/api/admin/cli/objects/ApiKey" \
  -H "Authorization: Bearer smk_..." \
  -H "Content-Type: application/json" \
  -d '{"label":"Shopware 6","role":"User","senderAddress":"rechnung@muster-logistik.de","allowedCidrs":["10.20.10.15/32"],"expiresInDays":180}'
var res = await http.PostAsJsonAsync("/api/admin/cli/objects/ApiKey", new
{
    label = "Shopware 6",
    role = "User",
    senderAddress = "rechnung@muster-logistik.de",
    allowedCidrs = new[] { "10.20.10.15/32" },
    expiresInDays = 180
});
var created = await res.Content.ReadFromJsonAsync<JsonElement>();
string oneTimeSecretKey = created.GetProperty("apiKey").GetString()!;
Objeto de colección (CRUD)

Route

Clave: domain RBAC: ServerAdmin+
10 propiedades

Enrutamiento SMTP saliente por dominio o excepción de certificado TLS (p. ej., StartTlsIgnoreCert para hosts MX de partners con certificados que no coinciden, o conector de smart host).

GET /api/admin/cli/objects/Route Lista todas las rutas salientes personalizadas
POST /api/admin/cli/objects/Route Crea una ruta de dominio personalizada o una anulación de TLS
PATCH /api/admin/cli/objects/Route/{domain} Actualiza el host de destino, el puerto o el modo TLS
DELETE /api/admin/cli/objects/Route/{domain} Elimina la ruta personalizada
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ domain string PRIMARY KEY
Dominio del destinatario (p. ej. "neff.me" o "*.partner-holding.eu").
├─ targetHost string OPCIONAL
Predeterminado: "" (Auto DNS-MX)
Nombre de host o IP del relay. Déjelo vacío para resolver automáticamente el MX de destino mediante DNS.
├─ targetPort integer OPCIONAL
Predeterminado: 25
Puerto TCP de destino (25, 587 o 465).
├─ tlsMode string OPCIONAL
Predeterminado: StartTlsOptional
Modo de TLS saliente y de validación X.509 para este dominio.
Enum:"StartTlsOptional""StartTlsRequired""StartTlsIgnoreCert""ImplicitTls""ImplicitTlsIgnoreCert""None"
├─ username string OPCIONAL
Nombre de usuario opcional de SMTP AUTH.
├─ password string SOLO ESCRITURA (SECRETO)
Contraseña SMTP AUTH opcional.
├─ hasPassword boolean SOLO LECTURA
Predeterminado: false
Indica si hay una contraseña almacenada.
├─ clearPassword boolean SOLO ESCRITURA (SECRETO)
Predeterminado: false
Establezca true para borrar la contraseña almacenada.
├─ description string OPCIONAL
Nota de auditoría del administrador.
├─ enabled boolean OPCIONAL
Predeterminado: true
Activar o pausar la ruta.
{
  "domain": "neff.me",
  "targetHost": "",
  "targetPort": 25,
  "tlsMode": "StartTlsIgnoreCert",
  "username": null,
  "description": "Shared-Hosting-MX mit abweichendem Zertifikatsnamen (Auto DNS-MX)",
  "enabled": true
}
# Partner-Domain mit fehlerhaftem MX-Zertifikat per Auto-DNS-MX freigeben:
nextxch-cli create Route \
  --set domain=neff.me \
  --set tlsMode=StartTlsIgnoreCert \
  --set description="TLS NameMismatch auf Partner-MX erlauben"

# Internes ERP-Subnetz an lokalen Connector routen:
nextxch-cli create Route \
  --set domain=erp.firma.intern \
  --set targetHost=10.40.2.15 \
  --set targetPort=25
curl -fsS -X POST "https://mail.firma.de/api/admin/cli/objects/Route" \
  -H "Authorization: Bearer smk_..." \
  -H "Content-Type: application/json" \
  -d '{"domain":"neff.me","targetHost":"","targetPort":25,"tlsMode":"StartTlsIgnoreCert","enabled":true}'
await http.PostAsJsonAsync("/api/admin/cli/objects/Route", new
{
    domain = "neff.me",
    targetHost = "",
    targetPort = 25,
    tlsMode = "StartTlsIgnoreCert",
    enabled = true
});
Política singleton

TransactionalSend (Singleton)

Clave: singleton RBAC: TenantAdmin+
11 propiedades

Controla el calentamiento progresivo de IP de 28 días, las categorías de mensajes y las cuotas horarias/diarias por proveedor del destinatario (Google, Microsoft, Yahoo/AOL, ISP alemanes, iCloud, *).

GET /api/admin/cli/objects/TransactionalSend/singleton Obtiene la política activa de calentamiento de IP y de cuotas por proveedor
PATCH /api/admin/cli/objects/TransactionalSend/singleton Actualiza el día de calentamiento, el preajuste o las cuotas por proveedor
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ enabled boolean OPCIONAL
Predeterminado: true
Activa la gestión de cuotas por categoría y por proveedor.
├─ warmingEnabled boolean OPCIONAL
Predeterminado: false
Activa la rampa progresiva diaria de calentamiento de IP.
├─ startAtWarmingDay integer SOLO ESCRITURA (SECRETO)
Predeterminado: 1
Salta al día N del calendario de calentamiento o lo restablece (1..365).
├─ currentWarmingDay integer SOLO LECTURA
Día activo actual en el plan de calentamiento.
├─ currentWarmingPercent integer SOLO LECTURA
Porcentaje activo actual de las cuotas del proveedor de destino.
├─ warmingDailyRampPercents integer[] OPCIONAL
Predeterminado: [5,7,10,...,100]
Matriz de porcentajes diarios para la rampa de calentamiento.
├─ maxDeferHours integer OPCIONAL
Predeterminado: 48
Ventana máxima de anticipación en horas para el ritmo de DeferQueue.
├─ defaultCategory string OPCIONAL
Predeterminado: transactional
Categoría de reserva cuando se omite en las llamadas a la API.
Enum:"critical""transactional""notification""marketing"
├─ preset string SOLO ESCRITURA (SECRETO)
Selector de preajustes en un solo paso.
Enum:"warming_28d""warming_14d""production_100"
├─ categories CategoryPolicy[] OPCIONAL
Predeterminado: 4 Kategorien
Array de objetos de política por categoría.
├─ providers ProviderQuota[] OPCIONAL
Predeterminado: 6 Provider-Gruppen
Array de definiciones de cuotas por proveedor de destinatarios.
{
  "enabled": true,
  "warmingEnabled": true,
  "startAtWarmingDay": 1,
  "maxDeferHours": 48,
  "defaultCategory": "transactional",
  "preset": "warming_28d"
}
# 28-Tage IP-Warming aktivieren:
nextxch-cli update TransactionalSend --set preset=warming_28d

# Direkt auf Warming-Tag 14 vorspringen:
nextxch-cli update TransactionalSend --set startAtWarmingDay=14
curl -fsS -X PATCH "https://mail.firma.de/api/admin/cli/objects/TransactionalSend/singleton" \
  -H "Authorization: Bearer smk_..." \
  -H "Content-Type: application/json" \
  -d '{"preset":"warming_28d","enabled":true}'
await http.PatchAsJsonAsync(
    "/api/admin/cli/objects/TransactionalSend/singleton",
    new { preset = "warming_28d", enabled = true });
Política singleton

ReputationShield (Singleton)

Clave: singleton RBAC: TenantAdmin+
20 propiedades

Protección opcional de la reputación saliente: lista automática de supresión de rebotes/quejas, baja con un clic según RFC 8058, cortacircuitos de rebotes, backoff adaptativo ante 421 de los ISP y webhooks HMAC-SHA256.

GET /api/admin/cli/objects/ReputationShield/singleton Obtiene la configuración actual de Reputation Shield
PATCH /api/admin/cli/objects/ReputationShield/singleton Actualiza los indicadores de protección o aplica un preajuste
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ preset string SOLO ESCRITURA (SECRETO)
Aplica un perfil de protección completo en un solo paso.
Enum:"off""recommended""strict_bulk"
├─ enableSuppressionPreCheck boolean OPCIONAL
Predeterminado: false
Filtra las direcciones suprimidas antes del envío.
├─ enforceSuppressionOnSmtpRelay boolean OPCIONAL
Predeterminado: false
Aplica también la lista de supresión en el envío SMTP (submission).
├─ allSuppressedBehavior string OPCIONAL
Predeterminado: reject_422
Modo de respuesta cuando todos los destinatarios están suprimidos.
Enum:"reject_422""accept_noop"
├─ autoSuppressHardBounces boolean OPCIONAL
Predeterminado: false
Suprime automáticamente los rebotes permanentes 5xx (hard bounces).
├─ hardBounceSuppressionDays integer OPCIONAL
Predeterminado: 180
Días de retención de las supresiones por rebote definitivo.
├─ autoSuppressConsecutiveSoftBounces boolean OPCIONAL
Predeterminado: false
Escala los rebotes temporales 4xx repetidos a la lista de supresión.
├─ softBounceEscalationThreshold integer OPCIONAL
Predeterminado: 5
Rebotes temporales consecutivos antes de la supresión.
├─ autoSuppressFblComplaints boolean OPCIONAL
Predeterminado: false
Analiza automáticamente los informes de abuso ARF entrantes según RFC 5965.
├─ enableOneClickUnsubscribe boolean OPCIONAL
Predeterminado: false
Inserta encabezados List-Unsubscribe RFC 8058 firmados con DKIM.
├─ oneClickUnsubscribeCategories string[] OPCIONAL
Predeterminado: ["marketing","notification"]
Categorías que reciben encabezados de baja RFC 8058.
├─ enableBounceCircuitBreaker boolean OPCIONAL
Predeterminado: false
Pausa los envíos no críticos si la tasa de rebotes/quejas de 24 h se dispara.
├─ maxHardBouncePercent number OPCIONAL
Predeterminado: 5.0
Límite de la tasa de rebotes permanentes en 24 h, en porcentaje.
├─ maxComplaintPercent number OPCIONAL
Predeterminado: 0.1
Límite máximo de la tasa de quejas ARF en 24 h, en porcentaje.
├─ allowCriticalWhenTripped boolean OPCIONAL
Predeterminado: true
Permite el correo 2FA crítico incluso cuando el disyuntor está disparado.
├─ enableAdaptiveIspBackoff boolean OPCIONAL
Predeterminado: false
Cooldown automático cuando el MX remoto devuelve una limitación 421/451.
├─ ispCooldownMinutes integer OPCIONAL
Predeterminado: 15
Duración del periodo de espera en minutos.
├─ webhookEnabled boolean OPCIONAL
Predeterminado: false
Activa los webhooks firmados con HMAC-SHA256.
├─ webhookUrl string OPCIONAL
URL HTTPS de destino para los webhooks.
├─ webhookSecret string SOLO ESCRITURA (SECRETO)
Secreto HMAC-SHA256 para X-Nextxch-Signature.
{
  "preset": "recommended",
  "enableSuppressionPreCheck": true,
  "autoSuppressHardBounces": true,
  "hardBounceSuppressionDays": 180,
  "enableOneClickUnsubscribe": true,
  "oneClickUnsubscribeCategories": ["marketing", "notification"],
  "enableBounceCircuitBreaker": true,
  "maxHardBouncePercent": 5.0,
  "maxComplaintPercent": 0.1,
  "allowCriticalWhenTripped": true,
  "enableAdaptiveIspBackoff": true,
  "webhookEnabled": true,
  "webhookUrl": "https://erp.firma.de/webhooks/nextxch"
}
# Empfohlenen Reputationsschutz aktivieren:
nextxch-cli update ReputationShield --set preset=recommended

# Webhook-Ziel konfigurieren:
nextxch-cli update ReputationShield \
  --set webhookEnabled=true \
  --set webhookUrl=https://erp.firma.de/webhooks/nextxch \
  --set webhookSecret=whsec_9f8e7d6c5b4a
curl -fsS -X PATCH "https://mail.firma.de/api/admin/cli/objects/ReputationShield/singleton" \
  -H "Authorization: Bearer smk_..." \
  -H "Content-Type: application/json" \
  -d '{"preset":"recommended"}'
await http.PatchAsJsonAsync(
    "/api/admin/cli/objects/ReputationShield/singleton",
    new { preset = "recommended" });
Política singleton

SpamGuard (Singleton)

Clave: singleton RBAC: ServerAdmin+
15 propiedades

Defensa de entrada en varias etapas: verificación SPF/DKIM/DMARC/ARC, Smart Greylisting (/24 IPv4 & /64 IPv6), clasificador ML en línea (FTRL-Proximal), zonas DNSBL, listas de permitidos/bloqueo y análisis TCP con ClamAV.

GET /api/admin/cli/objects/SpamGuard/singleton Obtiene los umbrales de SpamGuard & los contadores de entrenamiento
PATCH /api/admin/cli/objects/SpamGuard/singleton Actualiza in situ las puntuaciones, DNSBL, listas de permitidos o ClamAV
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ enableEmailAuthChecks boolean OPCIONAL
Predeterminado: true
Verifica SPF, DKIM, DMARC y ARC en el correo entrante.
├─ enableGreylisting boolean OPCIONAL
Predeterminado: true
Greylisting inteligente adaptativo para remitentes desconocidos que escriben por primera vez.
├─ greylistRetryDelaySeconds integer OPCIONAL
Predeterminado: 240
Retardo mínimo de reintento en segundos (predeterminado: 240 s).
├─ junkScoreThreshold number OPCIONAL
Predeterminado: 4.5
Umbral de puntuación para archivar en la carpeta Spam.
├─ rejectScoreThreshold number OPCIONAL
Predeterminado: 9.0
Umbral de puntuación para el rechazo definitivo con SMTP 550.
├─ tagSubjectOnJunk boolean OPCIONAL
Predeterminado: false
Antepone un prefijo al asunto de los mensajes de spam.
├─ subjectTagPrefix string OPCIONAL
Predeterminado: "[SPAM] "
Cadena del prefijo del asunto.
├─ enableStructuralHeuristics boolean OPCIONAL
Predeterminado: true
Evalúa heurísticas estructurales de MIME/HTML.
├─ dnsblZones string[] OPCIONAL
Predeterminado: ["bl.spamcop.net","ix.dnsbl.manitu.net"]
Zonas DNSBL consultadas.
├─ dnsblHitScore number OPCIONAL
Predeterminado: 3.5
Puntuación añadida por cada coincidencia en DNSBL.
├─ allowedSendersOrDomains string[] OPCIONAL
Predeterminado: []
Lista de permitidos explícita de correos o dominios remitentes.
├─ blockedSendersOrDomains string[] OPCIONAL
Predeterminado: []
Lista de bloqueo explícita de correos o dominios remitentes.
├─ clamAvEnabled boolean OPCIONAL
Predeterminado: false
Activa el análisis TCP INSTREAM del demonio ClamAV.
├─ clamAvHost string OPCIONAL
Predeterminado: "127.0.0.1"
Host o IP del demonio ClamAV.
├─ clamAvPort integer OPCIONAL
Predeterminado: 3310
Puerto TCP del daemon de ClamAV.
{
  "enableEmailAuthChecks": true,
  "enableGreylisting": true,
  "greylistRetryDelaySeconds": 240,
  "junkScoreThreshold": 4.5,
  "rejectScoreThreshold": 9.0,
  "enableStructuralHeuristics": true,
  "dnsblZones": ["bl.spamcop.net", "ix.dnsbl.manitu.net"],
  "dnsblHitScore": 3.5,
  "allowedSendersOrDomains": ["datev.de", "partner-spedition.de"],
  "blockedSendersOrDomains": [],
  "clamAvEnabled": false,
  "clamAvHost": "127.0.0.1",
  "clamAvPort": 3310
}
# Partner-Domains auf die Allowlist setzen:
nextxch-cli update SpamGuard \
  --set allowedSendersOrDomains=datev.de,partner-spedition.de

# Schwellenwerte anpassen:
nextxch-cli update SpamGuard --set junkScoreThreshold=4.5 --set rejectScoreThreshold=9.0
curl -fsS -X PATCH "https://mail.firma.de/api/admin/cli/objects/SpamGuard/singleton" \
  -H "Authorization: Bearer smk_..." \
  -H "Content-Type: application/json" \
  -d '{"junkScoreThreshold":4.5,"rejectScoreThreshold":9.0,"allowedSendersOrDomains":["datev.de"]}'
await http.PatchAsJsonAsync(
    "/api/admin/cli/objects/SpamGuard/singleton",
    new { junkScoreThreshold = 4.5, rejectScoreThreshold = 9.0 });
Política singleton

SmtpQueue (Singleton)

Clave: singleton RBAC: ServerAdmin+
19 propiedades

Controla la cola de entrega MX saliente, la concurrencia de workers, los intervalos de reintento, la limitación por ISP, la validación TLS saliente, el smart host global y los límites ESMTP entrantes.

GET /api/admin/cli/objects/SmtpQueue/singleton Obtiene toda la configuración de cola, TLS y ESMTP
PATCH /api/admin/cli/objects/SmtpQueue/singleton Actualiza los workers de la cola, el calendario de reintentos o los límites ESMTP en 0 ms
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ outboundQueuePaused boolean OPCIONAL
Predeterminado: false
Pausa la entrega MX saliente mientras sigue aceptando correo.
├─ outboundWorkerConcurrency integer OPCIONAL
Predeterminado: 8
Número de workers de entrega saliente en paralelo.
├─ maxDeliveryAttempts integer OPCIONAL
Predeterminado: 10
Número máximo de intentos de entrega antes del rebote permanente.
├─ retryScheduleMinutes integer[] OPCIONAL
Predeterminado: [5,15,60,240,720]
Calendario de espera entre reintentos, en minutos.
├─ maxConcurrentConnectionsPerMxDomain integer OPCIONAL
Predeterminado: 4
Máximo de conexiones simultáneas por dominio MX de destino.
├─ strictThrottleDomains string[] OPCIONAL
Predeterminado: ["t-online.de","gmx.de","web.de","yahoo.com"]
Dominios sujetos a una limitación estricta de concurrencia.
├─ strictThrottleMaxConcurrency integer OPCIONAL
Predeterminado: 2
Máximo de conexiones simultáneas para strictThrottleDomains.
├─ outboundConnectTimeoutSeconds integer OPCIONAL
Predeterminado: 30
Tiempo de espera de conexión TCP/TLS saliente en segundos.
├─ outboundBindAddress string OPCIONAL
Predeterminado: "" (Auto)
Dirección IP de origen local opcional para el enlace (bind).
├─ outboundUseStartTls boolean OPCIONAL
Predeterminado: true
Usar STARTTLS en las conexiones MX salientes.
├─ requireOutboundTls boolean OPCIONAL
Predeterminado: false
Interrumpir la entrega si el MX remoto no ofrece TLS.
├─ outboundTlsCertValidation string OPCIONAL
Predeterminado: Opportunistic
Modo global de validación de certificados X.509 salientes.
Enum:"Opportunistic""IgnoreErrors""Strict"
├─ outboundSmartHost string OPCIONAL
Predeterminado: ""
Host de relay ascendente global opcional.
├─ outboundSmartPort integer OPCIONAL
Predeterminado: 587
Puerto TCP del smart host global.
├─ maxMessageSizeMb long OPCIONAL
Predeterminado: 50
Límite global de ESMTP SIZE en MiB.
├─ maxRecipientsPerTransaction integer OPCIONAL
Predeterminado: 100
Número máximo de comandos RCPT TO por transacción.
├─ maxConnectionsPerIp integer OPCIONAL
Predeterminado: 20
Número máximo de conexiones entrantes simultáneas por IP.
├─ tarpitDelayMs integer OPCIONAL
Predeterminado: 1500
Retardo de tarpit en ms ante errores de autenticación o de comandos.
├─ requireTlsForAuth boolean OPCIONAL
Predeterminado: true
Exige cifrado TLS antes de permitir SMTP AUTH.
{
  "outboundQueuePaused": false,
  "outboundWorkerConcurrency": 8,
  "maxDeliveryAttempts": 10,
  "retryScheduleMinutes": [5, 15, 60, 240, 720],
  "maxConcurrentConnectionsPerMxDomain": 4,
  "strictThrottleDomains": ["t-online.de", "gmx.de", "web.de", "yahoo.com"],
  "strictThrottleMaxConcurrency": 2,
  "outboundUseStartTls": true,
  "requireOutboundTls": false,
  "outboundTlsCertValidation": "Opportunistic",
  "maxMessageSizeMb": 50,
  "maxRecipientsPerTransaction": 100,
  "tarpitDelayMs": 1500,
  "requireTlsForAuth": true
}
# Outbound-TLS auf Opportunistic (RFC 7435) setzen & MaxMessageSize auf 75 MiB erhöhen:
nextxch-cli update SmtpQueue \
  --set outboundTlsCertValidation=Opportunistic \
  --set maxMessageSizeMb=75
curl -fsS -X PATCH "https://mail.firma.de/api/admin/cli/objects/SmtpQueue/singleton" \
  -H "Authorization: Bearer smk_..." \
  -H "Content-Type: application/json" \
  -d '{"outboundTlsCertValidation":"Opportunistic","maxMessageSizeMb":75}'
await http.PatchAsJsonAsync(
    "/api/admin/cli/objects/SmtpQueue/singleton",
    new { outboundTlsCertValidation = "Opportunistic", maxMessageSizeMb = 75 });
Política singleton

TransportRules & Server (Singletons)

Clave: singleton RBAC: ServerAdmin+
11 propiedades

Aviso legal para toda la organización (insertado antes de la firma DKIM), script Sieve global de entrada, umbrales de bloqueo automático de Fail2Ban y ServerHostName canónico.

GET /api/admin/cli/objects/TransportRules/singleton Obtiene la configuración de aviso legal, Sieve global y bloqueo automático
PATCH /api/admin/cli/objects/TransportRules/singleton Actualiza las reglas de transporte in situ
GET /api/admin/cli/objects/Server/singleton Obtiene ServerHostName, motor de almacenamiento, profundidad de la cola y estado de la licencia
PATCH /api/admin/cli/objects/Server/singleton Actualiza el ServerHostName canónico en 0 ms
{ } Esquema de objetos y propiedades tipadas application/json · camelCase
├─ outboundDisclaimerEnabled boolean OPCIONAL
Predeterminado: false
Añade un pie de página corporativo obligatorio a los mensajes salientes.
├─ outboundDisclaimerExternalOnly boolean OPCIONAL
Predeterminado: true
Añadir el pie solo al enviar a dominios externos.
├─ outboundDisclaimerText string OPCIONAL
Predeterminado: ""
Pie legal en texto sin formato.
├─ outboundDisclaimerHtml string OPCIONAL
Predeterminado: ""
Pie legal en HTML.
├─ inboundSystemSieve string OPCIONAL
Predeterminado: ""
Script Sieve global RFC 5228 que se ejecuta antes de los scripts de usuario.
├─ autoBanThreshold integer OPCIONAL
Predeterminado: 8
Inicios de sesión fallidos antes del bloqueo automático de la IP.
├─ autoBanDurationMinutes integer OPCIONAL
Predeterminado: 30
Duración del bloqueo automático de IP en minutos.
├─ serverHostName string OPCIONAL
Predeterminado: mail.<domain>
FQDN canónico para EHLO, TLS y Autodiscover (objeto: Server).
├─ storageProvider string SOLO LECTURA
Predeterminado: Sqlite | SqlServer | Postgres
Motor de almacenamiento activo (objeto: Server).
├─ activeQueueCount integer SOLO LECTURA
Predeterminado: 0
Número actual de mensajes en la cola de salida (objeto: Server).
├─ licenseState string SOLO LECTURA
Predeterminado: EnterpriseActive
Estado de la licencia (objeto: Server).
{
  "outboundDisclaimerEnabled": true,
  "outboundDisclaimerExternalOnly": true,
  "outboundDisclaimerText": "-- \nMuster Logistik GmbH · Industriestraße 18 · 70565 Stuttgart · HRB 774819",
  "outboundDisclaimerHtml": "<p style='font-size:11px;color:#64748b'><strong>Muster Logistik GmbH</strong> &middot; 70565 Stuttgart &middot; HRB 774819</p>",
  "inboundSystemSieve": "require [\"fileinto\"];\nif header :contains \"X-Spam-Flag\" \"YES\" { fileinto \"Junk\"; }",
  "autoBanThreshold": 8,
  "autoBanDurationMinutes": 30
}
# Pflicht-Impressum & Auto-Ban aktualisieren:
nextxch-cli update TransportRules \
  --set outboundDisclaimerEnabled=true \
  --set autoBanThreshold=8

# Kanonischen Server-Hostnamen abfragen oder setzen:
nextxch-cli get Server
nextxch-cli update Server --set serverHostName=mail.muster-logistik.de
curl -fsS -X GET "https://mail.firma.de/api/admin/cli/objects/Server/singleton" \
  -H "Authorization: Bearer smk_..."
var status = await http.GetFromJsonAsync<JsonElement>("/api/admin/cli/objects/Server/singleton");
Console.WriteLine($"Host: {status.GetProperty("serverHostName")}, Queue: {status.GetProperty("activeQueueCount")}");