# TrackMailer — Documentación

Última revisión: 11 de octubre de 2026

[HTML](https://docs.trackmailer.io/es)

Email fiable para cada momento importante del cliente.

## Getting Started

1. Crea y verifica una única cuenta de Cloud. 2. Selecciona o crea la organización propietaria de la integración. 3. Crea un proyecto para el sitio, tienda o marca cuyos datos deban permanecer juntos. 4. Abre el espacio del producto y confirma que está habilitado antes de copiar credenciales o enviar tráfico de producción.

Con esto termina la configuración de la cuenta. Las secciones siguientes son guías de implementación. Las credenciales siempre pertenecen a un proyecto; los secretos de servidor nunca deben aparecer en código del navegador, repositorios públicos ni variables del cliente.

## Antes de integrar

El envío transaccional solo está disponible para organizaciones habilitadas con suscripción o autorización de servicio activa, dominio remitente verificado y clave Live activa. Las escrituras de contactos requieren contacts:write. Sandbox valida los mismos contratos del proyecto sin guardar contactos ni entregar correo.

## Crear y guardar una clave API

Abre TrackMailer → Claves API en Cloud. Un propietario o administrador selecciona Sandbox o Live, los scopes mínimos, los dominios remitentes permitidos y una vigencia de 1–365 días. El token completo se muestra una vez; guárdalo en el gestor de secretos del servidor que hará las llamadas.

Define TRACKMAILER_API_URL como https://us-east4-markengroup-cloud-services.cloudfunctions.net/trackmailerApi. Usa contacts:write para contactos, transactional:send para entrega transaccional y sequences:enroll para secuencias de grupos newsletter. Guarda el token en tu servidor; verifica una clave nueva antes de revocar la anterior. Envía Authorization: Bearer <token> con solicitudes JSON.

## Crear o actualizar un contacto

Envía la operación contacts.upsert por POST a $TRACKMAILER_API_URL/v1/requests. Una clave Live guarda el contacto del proyecto; una clave Sandbox devuelve status=validated, sent=false y persisted=false. El email identifica al contacto dentro del proyecto y externalId puede guardar la ID estable del sistema origen.

Attributes debe coincidir con campos definidos previamente en el espacio. Consent requiere estado, fuente y fecha ISO 8601 no futura. Una respuesta Live correcta contiene status=stored, contactId y revision.

### cURL

```bash
curl --request POST "$TRACKMAILER_API_URL/v1/requests" \
  --header "Authorization: Bearer $TRACKMAILER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
  "operation": "contacts.upsert",
  "contact": {
    "email": "alex@example.com",
    "name": "Alex Rivera",
    "externalId": "crm_123",
    "attributes": {},
    "listIds": [
      "LIST_ID"
    ],
    "consent": {
      "status": "subscribed",
      "source": "checkout",
      "recordedAt": "2026-09-08T15:00:00Z"
    }
  }
}'
```

### Node.js

```javascript
const response = await fetch(`${process.env.TRACKMAILER_API_URL}/v1/requests`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TRACKMAILER_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "operation": "contacts.upsert",
    "contact": {
      "email": "alex@example.com",
      "name": "Alex Rivera",
      "externalId": "crm_123",
      "attributes": {},
      "listIds": [
        "LIST_ID"
      ],
      "consent": {
        "status": "subscribed",
        "source": "checkout",
        "recordedAt": "2026-09-08T15:00:00Z"
      }
    }
  }),
});

if (!response.ok) throw new Error(`Request failed: ${response.status}`);
console.log(await response.json());
```

### Python

```python
import os
import requests

payload = {
    "operation": "contacts.upsert",
    "contact": {
        "email": "alex@example.com",
        "name": "Alex Rivera",
        "externalId": "crm_123",
        "attributes": {},
        "listIds": [
            "LIST_ID",
        ],
        "consent": {
            "status": "subscribed",
            "source": "checkout",
            "recordedAt": "2026-09-08T15:00:00Z",
        },
    },
}

response = requests.post(
    f"{os.environ['TRACKMAILER_API_URL']}/v1/requests",
    headers={
        "Authorization": f"Bearer {os.environ['TRACKMAILER_API_KEY']}",
        "Content-Type": "application/json",
    },
    json=payload,
    timeout=10,
)
response.raise_for_status()
print(response.json())
```

### PHP

```php
<?php

$payload = [
    'operation' => 'contacts.upsert',
    'contact' => [
        'email' => 'alex@example.com',
        'name' => 'Alex Rivera',
        'externalId' => 'crm_123',
        'attributes' => [],
        'listIds' => [
            'LIST_ID',
        ],
        'consent' => [
            'status' => 'subscribed',
            'source' => 'checkout',
            'recordedAt' => '2026-09-08T15:00:00Z',
        ],
    ],
];
$curl = curl_init(getenv('TRACKMAILER_API_URL') . '/v1/requests');

curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('TRACKMAILER_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
    CURLOPT_TIMEOUT => 10,
]);

$response = curl_exec($curl);
if ($response === false) {
    throw new RuntimeException(curl_error($curl));
}
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
if ($status >= 400) {
    throw new RuntimeException("Request failed: $status");
}
echo $response;
```

## Sincronizar preferencias de cuenta de ModelBoost

Usa contacts.sync únicamente desde el backend confiable de ModelBoostMDE con una clave contacts:write. externalId es la UID estable de Firebase; email es la dirección actual y previousEmail sirve como pista opcional al cambiarla. listIds es el conjunto completo deseado de grupos de destinatarios del proyecto, por lo que se eliminan las IDs omitidas. Se aceptan los grupos audience habituales del espacio y las listas newsletter explícitas; se rechazan otros tipos.

La fuente del consentimiento debe ser modelboost_account_preferences. Una suscripción explícita más reciente puede revertir una baja normal para los grupos seleccionados. Las supresiones por rebote, queja o abuso siguen activas. Los reintentos idénticos conservan contactId y revision; una preferencia anterior o un conflicto de identidad devuelve HTTP 409.

```bash
[object Object]
```

## Añadir o quitar de una lista

Crea la lista manual en TrackMailer → Audiencias → Grupos y copia la ID mostrada. contacts.upsert sigue siendo aditiva: listIds posteriores se combinan con las membresías existentes y un array vacío no elimina listas anteriores.

Solo contacts.sync funciona como contrato limitado de reemplazo de membresías para las preferencias de cuenta de ModelBoostMDE. Las demás integraciones deben seguir usando contacts.upsert o el panel y no deben asumir que el upsert aditivo elimina membresías.

## Baja y supresión

Para registrar una baja desde tu centro de preferencias, envía contacts.upsert con consent.status=unsubscribed, la fuente y el momento. Esto suprime el contacto y no equivale simplemente a quitarlo de una lista. Los contactos ya dados de baja, rebotados o denunciados nunca se reactivan con una importación posterior.

### cURL

```bash
curl --request POST "$TRACKMAILER_API_URL/v1/requests" \
  --header "Authorization: Bearer $TRACKMAILER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
  "operation": "contacts.upsert",
  "contact": {
    "email": "alex@example.com",
    "name": "Alex Rivera",
    "attributes": {},
    "listIds": [],
    "consent": {
      "status": "unsubscribed",
      "source": "preference-center",
      "recordedAt": "2026-09-08T16:00:00Z"
    }
  }
}'
```

### Node.js

```javascript
const response = await fetch(`${process.env.TRACKMAILER_API_URL}/v1/requests`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TRACKMAILER_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "operation": "contacts.upsert",
    "contact": {
      "email": "alex@example.com",
      "name": "Alex Rivera",
      "attributes": {},
      "listIds": [],
      "consent": {
        "status": "unsubscribed",
        "source": "preference-center",
        "recordedAt": "2026-09-08T16:00:00Z"
      }
    }
  }),
});

if (!response.ok) throw new Error(`Request failed: ${response.status}`);
console.log(await response.json());
```

### Python

```python
import os
import requests

payload = {
    "operation": "contacts.upsert",
    "contact": {
        "email": "alex@example.com",
        "name": "Alex Rivera",
        "attributes": {},
        "listIds": [],
        "consent": {
            "status": "unsubscribed",
            "source": "preference-center",
            "recordedAt": "2026-09-08T16:00:00Z",
        },
    },
}

response = requests.post(
    f"{os.environ['TRACKMAILER_API_URL']}/v1/requests",
    headers={
        "Authorization": f"Bearer {os.environ['TRACKMAILER_API_KEY']}",
        "Content-Type": "application/json",
    },
    json=payload,
    timeout=10,
)
response.raise_for_status()
print(response.json())
```

### PHP

```php
<?php

$payload = [
    'operation' => 'contacts.upsert',
    'contact' => [
        'email' => 'alex@example.com',
        'name' => 'Alex Rivera',
        'attributes' => [],
        'listIds' => [],
        'consent' => [
            'status' => 'unsubscribed',
            'source' => 'preference-center',
            'recordedAt' => '2026-09-08T16:00:00Z',
        ],
    ],
];
$curl = curl_init(getenv('TRACKMAILER_API_URL') . '/v1/requests');

curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('TRACKMAILER_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
    CURLOPT_TIMEOUT => 10,
]);

$response = curl_exec($curl);
if ($response === false) {
    throw new RuntimeException(curl_error($curl));
}
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
if ($status >= 400) {
    throw new RuntimeException("Request failed: $status");
}
echo $response;
```

## Preparar un dominio remitente

El espacio de trabajo acepta automáticamente el dominio del correo de acceso verificado y sus subdominios; la configuración no requiere aprobación del administrador de la plataforma. Añade el hostname exacto, como mail.example.com, en TrackMailer → Dominios remitentes. Publica el TXT de propiedad del proyecto y los registros DKIM, MAIL FROM MX y SPF mostrados; espera la verificación completa. Usa la senderDomainId de 64 caracteres mostrada en las claves y solicitudes.

Usa un subdominio dedicado por marca o flujo relevante. Cada host remitente sigue necesitando su propia prueba del proyecto y verificación completa; una clave solo puede enviar mediante senderDomainIds asociadas explícitamente.

## Anti-Spam Health: DKIM, SPF y DMARC

TrackMailer muestra preparación del dominio, autenticación y señales de entrega. Copia los valores DNS exactamente y coordina DMARC con la administración de correo. La autenticación reduce problemas evitables, pero no garantiza la bandeja de entrada.

## Enviar un email transaccional

Haz POST desde un servidor de confianza a $TRACKMAILER_API_URL/v1/email/send. La clave Live necesita transactional:send y debe permitir la senderDomainId elegida. La solicitud admite 1–20 destinatarios únicos, HTML y texto plano, reply-to opcional y los propósitos receipt, security o notification.

Define un header Idempotency-Key único de 8–128 caracteres para el mensaje lógico. Repetir la misma clave y body devuelve la misma messageId sin otra entrega; cambiar el contenido con la misma clave devuelve HTTP 409 idempotency_conflict. Una nueva solicitud aceptada devuelve HTTP 202 con status=queued.

### cURL

```bash
curl --request POST "$TRACKMAILER_API_URL/v1/email/send" \
  --header "Authorization: Bearer $TRACKMAILER_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: order_8472_confirmation_v1" \
  --data '{
  "senderDomainId": "SENDER_DOMAIN_ID",
  "fromLocalPart": "orders",
  "fromName": "Example Shop",
  "to": [
    {
      "email": "alex@example.com",
      "name": "Alex Rivera"
    }
  ],
  "replyTo": {
    "email": "support@example.com",
    "name": "Support"
  },
  "subject": "We received your order",
  "html": "<p>Thanks for your order.</p>",
  "text": "Thanks for your order.",
  "purpose": "receipt",
  "tags": {
    "MessageType": "order_confirmation",
    "Environment": "production"
  }
}'
```

### Node.js

```javascript
const response = await fetch(`${process.env.TRACKMAILER_API_URL}/v1/email/send`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TRACKMAILER_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "order_8472_confirmation_v1",
  },
  body: JSON.stringify({
    "senderDomainId": "SENDER_DOMAIN_ID",
    "fromLocalPart": "orders",
    "fromName": "Example Shop",
    "to": [
      {
        "email": "alex@example.com",
        "name": "Alex Rivera"
      }
    ],
    "replyTo": {
      "email": "support@example.com",
      "name": "Support"
    },
    "subject": "We received your order",
    "html": "<p>Thanks for your order.</p>",
    "text": "Thanks for your order.",
    "purpose": "receipt",
    "tags": {
      "MessageType": "order_confirmation",
      "Environment": "production"
    }
  }),
});

if (!response.ok) throw new Error(`Request failed: ${response.status}`);
console.log(await response.json());
```

### Python

```python
import os
import requests

payload = {
    "senderDomainId": "SENDER_DOMAIN_ID",
    "fromLocalPart": "orders",
    "fromName": "Example Shop",
    "to": [
        {
            "email": "alex@example.com",
            "name": "Alex Rivera",
        },
    ],
    "replyTo": {
        "email": "support@example.com",
        "name": "Support",
    },
    "subject": "We received your order",
    "html": "<p>Thanks for your order.</p>",
    "text": "Thanks for your order.",
    "purpose": "receipt",
    "tags": {
        "MessageType": "order_confirmation",
        "Environment": "production",
    },
}

response = requests.post(
    f"{os.environ['TRACKMAILER_API_URL']}/v1/email/send",
    headers={
        "Authorization": f"Bearer {os.environ['TRACKMAILER_API_KEY']}",
        "Content-Type": "application/json",
        "Idempotency-Key": "order_8472_confirmation_v1",
    },
    json=payload,
    timeout=10,
)
response.raise_for_status()
print(response.json())
```

### PHP

```php
<?php

$payload = [
    'senderDomainId' => 'SENDER_DOMAIN_ID',
    'fromLocalPart' => 'orders',
    'fromName' => 'Example Shop',
    'to' => [
        [
            'email' => 'alex@example.com',
            'name' => 'Alex Rivera',
        ],
    ],
    'replyTo' => [
        'email' => 'support@example.com',
        'name' => 'Support',
    ],
    'subject' => 'We received your order',
    'html' => '<p>Thanks for your order.</p>',
    'text' => 'Thanks for your order.',
    'purpose' => 'receipt',
    'tags' => [
        'MessageType' => 'order_confirmation',
        'Environment' => 'production',
    ],
];
$curl = curl_init(getenv('TRACKMAILER_API_URL') . '/v1/email/send');

curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('TRACKMAILER_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: order_8472_confirmation_v1',
    ],
    CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
    CURLOPT_TIMEOUT => 10,
]);

$response = curl_exec($curl);
if ($response === false) {
    throw new RuntimeException(curl_error($curl));
}
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
if ($status >= 400) {
    throw new RuntimeException("Request failed: $status");
}
echo $response;
```

## Planificar la API de boletines

Usa el planificador del espacio para envíos a clientes. Se prepara un contrato API independiente; obtén el endpoint y scope publicados en tu espacio antes de conectar un servidor. No deduzcas un endpoint de un ejemplo.

Conserva evidencia explícita de consentimiento subscribed por destinatario y respeta bajas, rebotes y quejas. Si la planificación API está disponible en tu proyecto, usa una clave de idempotencia estable.

## Validar antes de enviar

Usa una clave Sandbox contra /v1/requests para contacts.upsert, contacts.sync, transactional.send, sequence.enroll. Sandbox valida esquema, scope y recursos del proyecto, pero no guarda, envía, inscribe ni programa. Los endpoints productivos de envío exigen una clave Live.

## Respuestas, estados y reintentos

Guarda requestId y messageId de cada envío aceptado. Consulta Envíos en TrackMailer para distinguir queued, sent, delivered, bounced, complained, suppressed, failed o uncertain. HTTP 202 significa encolado de forma duradera, no entregado ni garantizado en la bandeja.

Reintenta 429 después del valor Retry-After. Reintenta errores 503 transitorios con backoff exponencial limitado, misma clave de idempotencia y mismo body. No reintentes 400, 401, 403, 402 o 409 hasta corregir validación, clave, scope, remitente, permiso, facturación o conflicto.

## Grupos, boletines y secuencias

Crea un grupo audience o newsletter, elige un dominio remitente verificado y registra el estado de suscripción. El espacio permite preparar un boletín único o una secuencia guardada; usa las IDs de grupo y remitente mostradas al integrar. La inscripción en secuencias se limita a grupos newsletter para que cada mensaje tenga una baja específica de lista.

Los enlaces guardados llevan parámetros de campaña TrackAny.Click y un utm_content distinto por paso. Entrega, apertura y clic son señales de TrackMailer; visitas y resultados del sitio consentidos son señales de TrackAny.Click. Las cifras de apertura pueden incluir proxies de privacidad y escáneres.

## Productos

- [TrackMailer](https://docs.trackmailer.io/es/index.md): Llega al cliente y mantén cada entrega claramente visible.
- [TrackAny.Click](https://docs.markengroup.cloud/es/trackany.md): Invierte en campañas y recorridos que crean valor.
- [TrackPost](https://docs.markengroup.cloud/es/trackpost.md): Crea contenido relevante con rapidez y mantén cada decisión clara.
- [LeadScoutEngine](https://docs.markengroup.cloud/es/leadscoutengine.md): Encuentra y prioriza empresas desde tus criterios y desbloquea solo la información necesaria.
