# Inicio

Te damos la bienvenida al sitio de desarrolladores de Perfit! En este sitio encontrarás todo lo necesario para utilizar nuestras APIs y conectar tu aplicación con Perfit.

## Crea tu cuenta

Antes que nada, para poder usar estas APIs, debes contar con una cuenta activa en Perfit.

{% hint style="info" %}
Si todavía no tienes una cuenta en Perfit puedes [crearla aquí](https://app.myperfit.com/#signup).
{% endhint %}

## APIs disponibles

Existen 2 APIs principales, que funcionan en forma independiente y cada una de ellas tiene sus particularidades, como por ejemplo la forma de autenticarse.

### Contacts API

Este API te permite gestionar los contactos, listas, intereses, campos personalizados o cualquier otra acción que puedas realizar en forma manual desde la interfaz de Perfit.

Los usos más frecuentes son crear nuevos contactos o actualizar contactos existentes para mantenerlos sincronizados con otros sistemas.

### Transactional API

{% hint style="warning" %}
**SERVICIO DISCONTINUADO**

La API de envíos transaccionales ya no está disponible para nuevas suscripciones.&#x20;

Para las suscripciones activas, **el servicio será discontinuado en su totalidad el día 1/3/2026.**
{% endhint %}

El API de envíos transaccionales te permite enviar emails desde tus sistemas, de a uno o varios, reemplazando contenido dinámico y muchas más opciones de personalización.&#x20;

Contamos un una API HTTP que cuenta con todas las funcionalidades y  es la forma recomendada de usarla. También contamos con una conexión SMTP para conectar sistemas existentes, pero sus funciones son más reducidas.

## Soporte a desarrolladores

En caso que tengas dudas, puedes contactarnos a <dev@myperfit.com>.


# Introducción

Si usaste un API REST alguna vez, vas a sentirte como en casa. Si no, en esta guía vas a encontrar todo lo necesario para hacerlo.

## Endpoint URLs

Para comunicarnos con el API, debemos hacer un pedido a la **URL base** del API, seguido de la versión de la api (siempre v2 por ahora), seguido del nombre de nuestra cuenta y el **namespace** correspondiente, es decir, el nombre del recurso al cual deseamos acceder.&#x20;

Los namespaces están en inglés, pero es muy sencillo reconocerlos. Puedes ver un listado completo de los namespaces disponibles en nuestro [listado de llamadas](https://perfitapiv2.docs.apiary.io/).

Por ejemplo, si necesitamos leer información de los contactos, entonces el namespace será `contacts` y la URL completa será algo como:

```
https://api.myperfit.com/v2/micuenta/contacts
```

## Métodos REST

Para leer o escribir información a través del API debemos hacer pedidos HTTP. Como las convenciones de REST indican, los métodos utilizados son los siguientes:

| Método     | URL                           | Efecto                            |
| ---------- | ----------------------------- | --------------------------------- |
| **GET**    | `/[account]/[namespace]`      | Obtener un listado de elementos   |
| **GET**    | `/[account]/[namespace]/[id]` | Obtener el detalle de un elemento |
| **POST**   | `/[account]/[namespace]`      | Crear un nuevo elemento           |
| **PUT**    | `/[account]/[namespace]/[id]` | Modificar un elemento             |
| **DELETE** | `/[account]/[namespace]/[id]` | Eliminar un elemento              |

Cada namespace funciona como una **colección** de elementos. Cada uno de esos elementos tiene un ID único.&#x20;

Al crear un elemento con un POST, el elemento recibe un ID automáticamente. Ese ID nos servirá para luego acceder a sus datos (GET), modificarlo (PUT) o eliminarlo (DELETE) utilizando la URL del namespace seguida del ID del elemento.&#x20;

Por ejemplo, si se trata del contacto 21, la URL sería:

```
https://api.myperfit.com/v2/micuenta/contacts/21
```


# Autenticación

La forma preferida de autenticación es utilizando el API key asociado a un usuario de Perfit.

## API key

Cada API key está asociado a un usuario en Perfit, por lo que contará con los mismos permisos que ese usuario.

Todos los llamados a la API deben incluir el header `Authorization: Bearer [APIKEY]`

Por ejemplo:

```
Authorization: Bearer micuenta-apikey12345678901234567890
```

{% hint style="info" %}
Para obtener el API key de un usuario puedes revisar [este artículo](https://docs.myperfit.com/es/articles/1437451-como-obtener-mi-api-key).
{% endhint %}

{% hint style="danger" %}
**NUNCA se debe incluir el API key en código del lado cliente, como por ejemplo javascript del navegador. Utilizarlo SIEMPRE del lado servidor (php, java, node, …)**
{% endhint %}

{% hint style="warning" %}
**Siempre que hagas una integración** **es recomendable crear un usuario dedicado** en Perfit, con los permisos mínimos necesarios para la tareas que necesites realizar. Además, resulta útil en caso  que necesites revocar el acceso en algún momento.
{% endhint %}


# Manejo de errores

Desafortunadamente, no siempre sale todo bien! Es importante detectar y manejar todos los errores en forma adecuada.

## Status codes

En caso de que ocurra un error, el status recibido será uno de los siguientes:

| **Code** | **Status**               | **Significado**                                                                  |
| -------- | ------------------------ | -------------------------------------------------------------------------------- |
| `400`    | `Bad Request`            | Petición inválida                                                                |
| `401`    | `Unauthorized`           | Las credenciales de acceso no son válidas                                        |
| `403`    | `Forbidden`              | El recurso solicitado no esta dentro de los permitidos                           |
| `404`    | `Not found`              | La URI solicitada no corresponde a ningun recurso                                |
| `405`    | `Method Not Allowed`     | El método HTTP no está soportado                                                 |
| `409`    | `Conflict`               | El recurso que se intenta crear o modificar entra en conflicto con uno existente |
| `415`    | `Unsupported Media Type` | El Content-Type del pedido no es soportado                                       |
| `500`    | `Internal Server Error`  | Error del servidor                                                               |
| `503`    | `Service Unavailable`    | El servidor está limitando el acceso al recurso                                  |

## Detalles del error

Como complemento, la respuesta contendrá un objeto `error` con una propiedad `status` coincidente con la del status HTTP y un `type` que indica el motivos específico del error.&#x20;

Por ejemplo:

```javascript
{
    "href": "/micuenta/contacts",
    "success": false,
    "error": {
        "status": 409,
        "type": "RESOURCE_EXISTS",
        "userMessage": "El pedido no puedo ser procesado ya que entra en conflicto con un recurso existente",
        "validationErrors": {
            "email": "Valor duplicado"
        }
    }
}
```

Los valores de `type` pueden ser los siguientes:

| **Type**                 | **Descripción**                                                                                                                                                       |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BAD_REQUEST`            | El pedido tiene una estructura inválida.                                                                                                                              |
| `RESOURCE_EXISTS`        | El recurso que se intentó crear entra en conflicto con uno existente. Se indica en `validationErrors` los campos en conflicto.                                        |
| `UNSUPPORTED_MEDIA_TYPE` | El formato de datos indicado no es soportado.                                                                                                                         |
| `INTERNAL_ERROR`         | Ocurrió un error interno del servidor.                                                                                                                                |
| `METHOD_NOT_ALLOWED`     | El método HTTP utilizado no es soportado por el recurso especificado.                                                                                                 |
| `NOT_FOUND`              | El recurso especificado no existe, puede ser porque el ID especificado no exista.                                                                                     |
| `SERVICE_UNAVAILABLE`    | El recurso solicitado no esta disponible temporalmente, posiblemente debido a un exceso de carga.                                                                     |
| `UNAUTHORIZED`           | No se proporcionó `token` de autorización, o es inválido.                                                                                                             |
| `FORBIDDEN`              | No se dispone del permiso necesario para el recurso solicitado.                                                                                                       |
| `VALIDATION_ERROR`       | El pedido realizado contiene errores en uno o más campos, en `validationErrors` se indican los campos con errores.                                                    |
| `ACCOUNT_REQUIRED`       | Se intentó realizar un login utilizando un email registrado en más de una cuenta, se debe repetir especificando la cuenta. En `data` se indican las cuentas posibles. |
| `PASSWORD_EXPIRED`       | Se intentó realizar un login pero la contraseña esta expirada o debe ser renovada.                                                                                    |


# Usos más frecuentes

En esta sección encontrarás los usos más comunes. Si no encuentras lo que buscas, avísanos así podemos agregar más ejemplos útiles.

Para ver el listado todos los de endpoints disponibles, con todos sus parámetros y opciones visita la [referencia completa de la API](https://perfitapiv2.docs.apiary.io/#)

{% hint style="info" %}
Para simplicidad de los ejemplos, no estamos manejando los errores que pueden producirse al hacer las llamadas a la API.&#x20;

En un ambiente productivo es importante capturar los errores de forma adecuada.
{% endhint %}

#### Sobre los ejemplos en Node.js

Para los ejemplos en Node.js utilizaremos la librería [axios](https://github.com/axios/axios), por su simplicidad. Deberás instarla de esta forma:

```bash
npm install axios
```

y luego la puedes incluir así:

```javascript
const axios =  require('axios');
```

## Ejemplos:

{% content-ref url="/pages/-MLOUZdtsu0moaXeR5vd" %}
[Crear o actualizar un contacto en una lista](/contacts-api/usos-mas-frecuentes/crear-o-actualizar-un-contacto-en-una-lista)
{% endcontent-ref %}

{% content-ref url="/pages/-MLOUtbGC-AVlw7uKPFK" %}
[Modificar un contacto existente](/contacts-api/usos-mas-frecuentes/modificar-un-contacto-existente)
{% endcontent-ref %}

{% content-ref url="/pages/-MLOVBMAc36RQ4Wh-zvG" %}
[Agregar un interés a un contacto](/contacts-api/usos-mas-frecuentes/agregar-un-interes-a-un-contacto)
{% endcontent-ref %}

{% content-ref url="/pages/-MLOVJvKuTRhgEvfb1VD" %}
[Desuscribir a un contacto](/contacts-api/usos-mas-frecuentes/desuscribir-a-un-contacto)
{% endcontent-ref %}


# Crear o actualizar un contacto en una lista

Este es el uso más frecuente, útil para cargar nuevos contactos desde un desarrollo propio, como un formulario o cualquier otro sistema que necesite crear contactos en Perfit.

Tanto la lista, como los intereses y campos personalizados ya deben haber sido creados previamente. En todos estos casos, vamos a referenciarlos usando su `id`.

**Para crear un contacto, usamos el método POST** y la respuesta nos devuelve la información completa del nuevo contacto.

{% tabs %}
{% tab title="Node.js" %}

```javascript
const axios =  require('axios');

const listId = 123;
const account = 'micuenta';
const apiKey = 'micuenta-123456789023467890';

const axiosConfig = { headers: { Authorization: `Bearer ${apiKey}` } };

const contactData = {
    email: 'test@example.com',
    firstName: 'Nombre', 
    lastName: 'Apellido',
    customFields: [
        {id: 10, value: 'valor campo personalizado 1'},
        {id: 11, value: 'valor campo personalizado 2'}
    ],
    interests: [
        {id: 3}, {id: 5}
    ]
}

axios.post(
    `https://api.myperfit.com/v2/${account}/lists/${listId}/contacts`,
    contactData, 
    axiosConfig
).then(response => {
    const contact = response.data.data;
    console.log('Contacto creado/actualizado', contact);    
}); 
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php

$listId = 123;
$account = 'micuenta';
$apiKey = 'micuenta-123456789023467890';

$response = file_get_contents(
    "https://api.myperfit.com/v2/$account/lists/$listId/contacts" ,
    false,
    stream_context_create(['http'=> [
        'method'=>'POST',
        'header' => "Content-Type: application/json\r\n" .
                    "Authorization: Bearer $apiKey",
        'content' => '{
            "email": "test@example.com",
            "firstName": "Nombre",
            "lastName": "Apellido",
            "customFields": [
                { "id": 12, "value": "valor campo personalizado 1" },
                { "id": 13, "value": "valor campo personalizado 2" }
            ],
            "interests": [{"id": 2}, {"id": 3}]
        }'
    ]])
);

var_dump($response);
```

{% endtab %}

{% tab title="cURL" %}

```bash
curl -X POST \
  https://api.myperfit.com/v2/micuenta/lists/123/contacts \
  -H 'Authorization: micuenta-123456789023467890' \
  -H 'Content-Type: application/json' \
  -d '
{"email": "test@example.com",
"firstName": "Nombre",
"lastName": "Apellido",
"customFields": [
{ "id": 12, "value": "valor campo personalizado 1" },
{ "id": 13, "value": "valor campo personalizado 2" }
],
"interests": [{"id": 2}, {"id": 3}]
}'
```

{% endtab %}
{% endtabs %}


# Modificar un contacto existente

En este caso queremos modificar el nombre y un campo personalizado de un contacto específico.

Como ID de contacto podemos utilizar tanto la dirección de email, como su ID numérico, en este caso usaremos el email, que es lo más frecuente.

**Para modificar un contacto, usamos el método PUT**, y nos devuelve sólo la información modificada.

{% tabs %}
{% tab title="Node.js" %}

```javascript
const axios =  require('axios');

const account = 'micuenta';
const apiKey = 'micuenta-123456789023467890';

const axiosConfig = { headers: { Authorization: `Bearer ${apiKey}` } };

const email = 'test@example.com';
const contactData = {
    firstName: 'Nombre', 
    customFields: [
        {id: 10, value: 'valor campo personalizado 1'}
    ]
}

axios.put(
    `https://api.myperfit.com/v2/${account}/contacts/${email}`,
    contactData, 
    axiosConfig
).then(response => {
    const contact = response.data.data;
    console.log('Datos modificados', contact);    
}); 
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php

$account = 'micuenta';
$apiKey = 'micuenta-123456789023467890';
$email = 'test@example.com';

$response = file_get_contents(
    "https://api.myperfit.com/v2/$account/contacts/$email" ,
    false,
    stream_context_create(['http'=> [
        'method'=>'PUT',
        'header' => "Content-Type: application/json\r\n" .
                    "Authorization: Bearer $apiKey",
        'content' => '{
            "firstName": "Nombre",
            "customFields": [
                { "id": 12, "value": "valor campo personalizado 1" }
            ]
        }'
    ]])
);

var_dump($response);
```

{% endtab %}

{% tab title="cURL" %}

```bash
curl -X PUT \
  https://api.myperfit.com/v2/micuenta/contacts/test@example.com \
  -H 'Authorization: micuenta-123456789023467890' \
  -H 'Content-Type: application/json' \
  -d '{"firstName": "Nombre",
"customFields": [{ "id": 12, "value": "valor campo personalizado 1" }]}'
```

{% endtab %}
{% endtabs %}

Si en este ejemplo especificas un array con intereses como hicimos en el primer ejemplo, esos intereses van a pisar a los que ya tenga el contacto. En el siguiente ejemplo veremos como hacer para agregar un interés o lista al contacto sin afectar a los que ya tiene asociados.


# Agregar un interés a un contacto

Este ejemplo permite agregar un interés a un contacto (también puedes hacer lo mismo con las listas) sin afectar a los demás intereses que pueda tener asociado.

El interés ya debe existir y necesitas su Id para asociarlo al contacto. En este caso, no es necesario incluir un contenido en el body del PUT.

{% tabs %}
{% tab title="Node.js" %}

```javascript
const axios =  require('axios');

const account = 'micuenta';
const apiKey = 'micuenta-123456789023467890';

const axiosConfig = { headers: { Authorization: `Bearer ${apiKey}` } };

const email = 'test@example.com';
const interestId = 123;

axios.put(
    `https://api.myperfit.com/v2/${account}/contacts/${email}/interests/${interestId}`,
    null, 
    axiosConfig
).then(response => {
    const contact = response.data.data;
    console.log('Datos modificados', contact);    
}); 
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php

$account = 'micuenta';
$apiKey = 'micuenta-123456789023467890';
$email = 'test@example.com';
$interestId = 123;

$response = file_get_contents(
    "https://api.myperfit.com/v2/$account/contacts/$email/interests/$interestId" ,
    false,
    stream_context_create(['http'=> [
        'method'=>'PUT',
        'header' => "Content-Type: application/json\r\n" .
                    "Authorization: Bearer $apiKey"
    ]])
);

var_dump($response);
```

{% endtab %}

{% tab title="cURL" %}

```bash
curl -X PUT \
  https://api.myperfit.com/v2/micuenta/contacts/test@example.com/interests/123 \
  -H 'Authorization: micuenta-123456789023467890' \
  -H 'Content-Type: application/json'
```

{% endtab %}
{% endtabs %}


# Desuscribir a un contacto

Este ejemplo resulta muy útil cuando queremos marcar un contacto en Perfit como desuscripto, para no enviarle más comunicaciones.

{% hint style="info" %}
Una vez que el contacto es marcado como desuscripto no se le enviarán más emails. Esta acción no puede revertirse, la única forma es que el mismo contacto se re-suscriba a través de un formulario optin.
{% endhint %}

{% tabs %}
{% tab title="Node.js" %}

```javascript
const axios =  require('axios');

const account = 'micuenta';
const apiKey = 'micuenta-123456789023467890';

const axiosConfig = { headers: { Authorization: `Bearer ${apiKey}` } };

const email = 'test@example.com';

axios.post(
    `https://api.myperfit.com/v2/${account}/contacts/${email}/unsubscribe`,
    null, 
    axiosConfig
).then(response => {
    const contact = response.data.data;
    console.log('Datos modificados', contact);    
}); 
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php

$account = 'micuenta';
$apiKey = 'micuenta-123456789023467890';
$email = 'test@example.com';

$response = file_get_contents(
    "https://api.myperfit.com/v2/$account/contacts/$email/unsubscribe" ,
    false,
    stream_context_create(['http'=> [
        'method'=>'POST',
        'header' => "Content-Type: application/json\r\n" .
                    "Authorization: Bearer $apiKey"
    ]])
);

var_dump($response);
```

{% endtab %}

{% tab title="cURL" %}

```bash
curl -X POST \
  https://api.myperfit.com/v2/micuenta/contacts/test@example.com/unsubscribe \
  -H 'Authorization: micuenta-123456789023467890' \
  -H 'Content-Type: application/json'
```

{% endtab %}
{% endtabs %}


# Introducción

Los Custom Triggers permiten disparar automations utilizando eventos enviados desde sistemas externos via API.

## Eventos

Utilizando custom triggers puedes enviar eventos asociados a tus contactos y utilizarlos para iniciar automations personalizados.

La estructura básica de un custom trigger es:

```json
{
  "trigger_key": "my_custom_event",
  "contact": "contact@domain.com",
  "context": {}
}
```

* **trigger\_key:** Es el identificador de tipo de evento, con el cual debes configurar los automations que quieres iniciar. Puede ser cualqueir cadena alfanuérica minúscula de hasta 30 caracteres, puedes incluir guin bajo también.
* **contact:** La dirección de email del contacto asociado. En caso que no exista en tu cuenta de Perfit, será creado.
* **context**: Datos adiciones para utilizar dentro del contenido del email. Ver ejemplo a continuación.

{% hint style="info" %}
El uso de los custom triggers está limitado a la recepción de **un máximo de** **200 eventos por hora.** Superado este volumen, la API responderá error 429 (too many requests) y los eventos serán descartados.
{% endhint %}

## Contexto

Adicionalmente, es posible incluir un contexto en el evento:

```json
{
  "trigger_key": "my_custom_event",
  "contact": "contact@domain.com",
  "context": {
    "key1": "value1",
    "key2": "value2"
  }
}
```

Esta información será incluida en el contexto del automation y podrá ser utilizada para personalizar el contenido y comportamiento.


# Activación y envío de eventos

## Activación

Para empezar a utilizar Custom Triggers, primero debes activar la integración desde la sección [Integraciones > Custom Triggers ](https://app.myperfit.com/integrations/customtriggers)de tu cuenta de Perfit.

Una vez activa verás un url como esta:

<figure><img src="https://9280380-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LJf2JoD9WEoo2euTccs%2Fuploads%2F1UVFK5e2E75NNxWpDCqr%2Fimage.png?alt=media&amp;token=772dc90c-a445-4142-9edc-5e4ee451fd6a" alt=""><figcaption></figcaption></figure>

## Envío de eventos

Utiliza la URL obtenida para enviar los eventos mediante un POST con formato JSON

{% hint style="info" %}
El uso de los custom triggers está limitado a la recepción de **un máximo de** **200 eventos por hora.** Superado este volumen, la API responderá error 429 (too many requests) y los eventos serán descartados.
{% endhint %}

Por ejemplo:

```
POST https://webhooks.myperfit.net/events/customtriggers/micuenta/init/2d1be08e/6fc80952
```

```json
{
  "trigger_key": "my_custom_event",
  "contact": "contact@domain.com",
  "context": {
    "key1": "value1",
    "key2": "value2"
  }
}
```

{% hint style="success" %}
Recuerda reemplazar la URL con la obtendia al activar la integración.
{% endhint %}

Ejemplo en cURL:

```
curl --location 'https://webhooks.myperfit.net/events/customtriggers/micuenta/init/2d1be08e/6fc80952' \
--header 'Content-Type: application/json' \
--data-raw '{
  "trigger_key": "my_custom_event",
  "contact": "contact@domain.com",
  "context": {
    "key1": "value1",
    "key2": "value2"
  }
}'
```


# Disparo de automations

{% hint style="info" %}
Sólo se iniciarán los automations cuyo custom trigger key coincida exactamente con el del evento recibido.
{% endhint %}

Para utilizar los custom triggers debes primero crear automations con este tipo de disparadores.&#x20;

Luego de activar la integración verás una nueva categoría dentro del catálogo de automations:

<figure><img src="https://9280380-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LJf2JoD9WEoo2euTccs%2Fuploads%2FybfX21RAdLy2l4cH1i9h%2Fimage.png?alt=media&amp;token=720c307b-ba3c-40cf-b639-8293b1c994c0" alt=""><figcaption></figcaption></figure>

Edita el automation creado y define la trigger\_key con la cual quieres iniciarlo:

<figure><img src="https://9280380-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LJf2JoD9WEoo2euTccs%2Fuploads%2FeuCylggpMhob93aJe8tT%2Fimage.png?alt=media&amp;token=193168fa-1f1c-4326-bbdb-97f1eb58733e" alt=""><figcaption></figcaption></figure>

<figure><img src="https://9280380-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LJf2JoD9WEoo2euTccs%2Fuploads%2Fvzt7gCfxyya14fGuI0ma%2Fimage.png?alt=media&amp;token=ff264b80-fef6-483d-8c91-43cb39199af6" alt=""><figcaption></figcaption></figure>

Recuerda activar el automation una vez completada la configuración. Los automations inactivos no serán disparados por más que se reciban eventos.


# Utilizando el contexto

Puedes utilizar la información incluida en el context del trigger dentro del contenido de tus mensajes.

Por ejemplo, para este evento:

```
{
  "trigger_key": "my_custom_event",
  "contact": "contact@domain.com",
  "context": {
    "coupon": "1234"
  }
}
```

Puedes utilizar este código para reemplazar el valor 1234 dentro del asunto o contenido de los emails enviados:

```
Tu cupón de descuento es: ${coupon}
```

Adicionalmente, podrás utlizar toda la información disponible asociada al contacto:

```
${contact.email}
${contact.first_name}
...
```


# Introducción

La API de envíos transaccionales te permite enviar los emails de tu aplicación a través de Perfit.

{% hint style="warning" %}
**SERVICIO DISCONTINUADO**

La API de envíos transaccionales ya no está disponible para nuevas suscripciones.&#x20;

Para las suscripciones activas, **el servicio será discontinuado en su totalidad el día 1/3/2026.**
{% endhint %}

## Activa tu cuenta

Para empezar a utilizar el servicio de emails transaccionales, primero **debes generar tu API key**. Puedes hacerlo desde la sección **Integraciones** en tu cuenta de Perfit.&#x20;

Si tienes dudas sobre cómo hacerlo contáctanos a <soporte@myperfit.com>.

{% hint style="info" %}
El API key utilizada para los envíos transaccionales **es una API key específica**, distinta a la asociada a cada usuario (la que se usa para gestión de contactos por ejemplo)

Es fácil identificar sin un API key es para uso de envíos transaccionales, si contiene `tr` luego del nombre de cuenta:

`micuenta-tr-aDsffD35eSdfsadsGFdFfssfsaADS`
{% endhint %}

## Usos frecuentes

Algunos de los usos más frecuentes son:

* Emails de registración, bienvenida, …
* Emails de compra finalizada, carrito abandonado, …
* Notificaciones de cambio de plan, factura pendientes, pago realizado…
* Envío de notificaciones en general.

## Características principales

### Potente motor de reemplazo de contenido

Utiliza variables de reemplazo para enviar contenidos personalizados a cada destinatario. Contamos con uno de los motores más completos, que te permitirá entre otra cosas:

* **Bloques condicionales**. Por ejemplo, para mostrar ciertas partes del contenido dependiendo del perfil de cada destinatario.
* **Iteradores**. Muy útil para mostrar listados variables de productos.
* **Reemplazo de variables con valores por defecto.** Para dirigirte a cada destinatario en forma personal
* **Aplicar formatos a fechas, números, etc.**&#x20;

### Monitoreo de aperturas y clicks

Registramos en forma predeterminada las aperturas y clicks en todos los links del contenido.

### Gestión de desuscripciones

Opcionalmente puedes incluir un link en el contenido para que tus contactos se desuscriban.

### Versión online del contenido

También generamos una versión web del contenido. Así podrás incluir un link para ver el mensaje en el navegador.

### Notificaciones por webhooks

Recibe todos los eventos de envíos, aperturas, clicks y desuscripciones para sincronizar tus listas o actualizar el estado de tus contactos en la aplicación integrada.

### Envío programado

Es posible posponer el envío de los emails indicando una fecha futura.&#x20;

### Archivos adjuntos

Puedes incluir archivos adjuntos en tus envíos. Soportamos los formatos pdf, png, jpg, gif, txt, csv, xls, xlsx, doc, docx.

{% hint style="info" %}
Para habilitar el envío de archivos adjuntos, ponte en contacto con nosotros. Puedes solicitarlo a <dev@myperfit.com>.
{% endhint %}


# Envío usando HTTP

La API HTTP permite enviar un contenido a uno o varios destinatarios con un sólo POST, utilizar variables de reemplazo para personalizarlos, e incluir etiquetas y atributos para su seguimiento.

{% hint style="warning" %}
**SERVICIO DISCONTINUADO**

La API de envíos transaccionales ya no está disponible para nuevas suscripciones.&#x20;

Para las suscripciones activas, **el servicio será discontinuado en su totalidad el día 1/3/2026.**
{% endhint %}

## Ejemplo básico

Empecemos por el ejemplo más sencillo posible:

```bash
curl -X POST \
  https://transactional.myperfit.com/v1/mail/send \
  -H 'Authorization: MI_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "from": { "email": "remitente@example.com" },
    "subject": "Asunto de prueba",
    "content": {"html": "<h1>Funciona! 💪</h1>"},
    "recipients": [{"to": {"email": "recipient@example.com"}}]
}'
```

## Ejemplo usando una plantilla

Otra forma de indicar el contenido es utilizando una plantilla diseñada en la aplicación web, donde ya incluyen el remitente asunto y contenido.

```bash
curl -X POST \
  https://transactional.myperfit.com/v1/mail/send \
  -H 'Authorization: MI_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "template_id": "etpl_efd23wnfo23edsoirsnde",
    "recipients": [{"to": {"email": "recipient@example.com"}}]
}'
```

## /mail/send

<mark style="color:green;">`POST`</mark> `https://transactional.myperfit.com/v1/mail/send`

Este endpoint permite encolar para su envío uno o varios emails que compartan el mismo contenido. \
\
Es posible enviar a hasta 1000 destinatarios (`recipients`) en un mismo request.\
\
El `content`,  `subject` y `headers` pueden ser personalizados utilizando etiquetas de reemplazo del estilo `${object.key}`.  <br>

#### Headers

| Name          | Type   | Description     |
| ------------- | ------ | --------------- |
| Authorization | string | Bearer API\_KEY |

#### Request Body

| Name | Type   | Description                        |
| ---- | ------ | ---------------------------------- |
| Body | object | Cuerpo del mensaje en formato JSON |

{% tabs %}
{% tab title="202 En caso de aceptar el pedido de envío." %}

```javascript
{
    "success": true,
    "data": ""
}
```

{% endtab %}
{% endtabs %}

### Estructura del mensaje

Los únicos parámetros requeridos del body son: **`from.email`**, **`subject`**, **`content`** (al menos uno: `html` o `text`) y **`recipients`** (al menos uno, incluyendo al menos **`to.email`**).

* **`from`**: **Object, requerido**. **Email y nombre del remitente.**
  * **`email`**: **String, requerido**.
  * `name`: String, opcional.
* `reply_to`: Object, opcional. Dirección y nombre de respuesta.
  * `email`: String, requerido.
  * `name`: String, opcional.
* **`subject`**: **String, requerido, max 200 chars. Asunto del correo.**
* **`content`**: **Object, requerido**. **Se debe indicar al menos un tipo.**
  * `html`: String, opcional, max 300KB. Contenido de tipo `text/html`.&#x20;
  * `text`: String, opcional, max 300KB. Contenido de tipo `text/plain`.
* `template_id`**: String, opcional. El id de la plantilla a utilizar, en lugar de indicar el `content.`** En caso de usar una plantilla, dejan de ser requeridos los campos from, reply\_to, subject y content. En caso de indicar alguno de ellos, sus valores reemplazarán a los definidos en la plantilla.
* `attachments`: Array de objects, opcional.&#x20;
  * `file_name`: String, requerido. Nombre del archivo adjunto.
  * `mime_type`: String, requerido. Tipo mime del archivo adjunto.
  * `data`: String, requerido. Contenido del archivo adjunto en base64.
* `headers`: Object, opcional. Mapa string-string con headers adicionales a incluir.
* **`recipients`: Array de objetos, requerido**. **Debe contener al menos un elemento.**
  * **`to`**: **Object, requerido. Email y nombre del destinatario.**
    * **`email`**: **String, requerido**.
    * `name`: String, opcional.
  * `cc`: Array de objects, opcional. Listado de destinatarios en copia.
    * `email`: String, requerido.
    * `name`: String, opcional
  * `bcc`: Array de objects, opcional. Misma estructura que el cc. Listado de destinatarios en copia oculta.
  * `substitutions`: Object, opcional. Modelo de reemplazo asociado a este destinatario.
  * `custom_args`: Object, opcional. Mapa string-string con información de identificación y seguimiento. Se informarán junto con los eventos de monitoreo.
  * `tags`: Array de strings, opcional. Etiquetas de identificación y seguimiento de este batch. Se informarán junto con los eventos de monitoreo.
* `substitutions`: Object, opcional. Modelo de reemplazo asociado a todo el batch.
* `tracking`: Object, opcional.
  * `open`: Object, opcional.
    * `enable`: Boolean, opcional, default: `true`. Activar monitoreo de aperturas.
  * `click`: Object, opcional.
    * `enable`: Boolean, opcional, default: `true` Activar monitoreo de clicks.
  * `ganalytics`: Object, opcional. Códigos de seguimiento para Google Analytics.
    * `utm_source`: String, opcional.
    * `utm_medium`: String, opcional.
    * `utm_campaign`: String, opcional.
    * `utm_content`: String, opcional.
    * `utm_term`: String, opcional.
* `batch_code`: String, opcional. Identificador alfanumérico (se limpan todos los caracteres que no sean \[a-z0-9]).&#x20;
* `tags`: Array de strings, opcional. Etiquetas de identificación y seguimiento de este batch. Se informarán junto con los eventos de monitoreo.&#x20;
* `launch_date`: Fecha. Posponer el envío de este batch hasta la fecha y hora indicadas. Si no se indica se envía en forma inmediata.

Este objeto JSON incluye todas las opciones mencionadas.

```javascript
{
	"from": {
		"email": "diego@perfit.com.ar", 
		"name": "Diego"
	},
	"reply_to": {
		"email": "soporte@myperfit.com", 
		"name": "Diego"
	},
	"subject": "Hola ${contact.first_name}, este es el asunto",
	"content": {
		"html": "<!DOCTYPE ...><html><body><h1>Hola mundo!</h1></body></html>",
		"text": "Contenido de tipo texto"
	},
	"headers": {
		"X-My-Header": "my custom header value" 
	},
	"recipients" : [
		{
			"to": { 
				"email": "rcpt@example.com",
				"name": "Nombre Recipient"
			},
			"cc": [ 
				{ 
					"email": "cc1@example.com",
					"name": "Nombre CC1"
				},
				{ 
					"email": "cc2@example.com",
					"name": "Nombre CC2"
				}				
			],
			"bcc": [ 
				{ 
					"email": "bcc1@example.com",
					"name": "Nombre BCC1"
				},
				{ 
					"email": "bcc2@example.com",
					"name": "Nombre BCC2"
				},				
			],
			"substitutions": {
				"contact": {
					"email": "diego@myperfit.com",
					"name": "Diego",
					"gender": "M",
					"age": 35,
					"ciudad": "Buenos Aires",
				}
			},
			"custom_args": {
				"internal_id": "43231312",
				"other_attr": "1234"
			},
			"tags": ["cliente frecuente"]
		}
	],
	"substitutions": {
		"account": {
			"business_name": "Perfit",
			"address": "San Nicolas 3940, CABA, Argentina",
		}
	},
	"attachments": [
  	{
  		"file_name": "file.png",
	    "mime_type": "image/png",
    	"data": "base64data"
    }
  ],     
	"tracking": { 
		"open": { 
			"enable": true 
		},
		"click": { 
			"enable": true
		},
		"ganalytics": {
			"utm_source": "Perfit",
			"utm_medium": "email",
			"utm_campaign": "my campaign"
		}
	},
	"batch_code": "mycampaign1234",
	"tags": ["electro"],
	"launch_date": "2019-08-20T13:30:00Z",
}

```

{% hint style="success" %}
Cuando es necesario hacer un gran número de requests, es altamente recomendable **mantener las conexiones HTTP abiertas usando keep-alive**. Esto evita todo el overhead que se introduce al establecer las conecciones TCP. En las pruebas realizadas se vieron incrementos de \~5x en los requests por segundo alcanzados.
{% endhint %}


# Autenticación

{% hint style="warning" %}
**SERVICIO DISCONTINUADO**

La API de envíos transaccionales ya no está disponible para nuevas suscripciones.&#x20;

Para las suscripciones activas, **el servicio será discontinuado en su totalidad el día 1/3/2026.**
{% endhint %}

{% hint style="info" %}
El API key utilizada para envíos transaccionales **es una API key específica**, distinta a la asociada a cada usuario.

Es fácil identificar sin un API key es para uso de envíos transaccionales, si contiene `tr` luego del nombre de cuenta:

`micuenta-tr-aDsffD35eSdfsadsGFdFfssfsaADS`
{% endhint %}

## Authorization header&#x20;

La autenticación se realiza agregando el header `Authorization` a cada request:&#x20;

```
Authorization: Bearer MI_API_KEY
```

#### Por ejemplo:

```
Authorization: Bearer micuenta-tr-aDsffD35eSdfsadsGFdFfssfsaADS
```

{% hint style="danger" %}
**NUNCA se debe incluir el API key en código del lado cliente, como por ejemplo javascript del navegador. Utilizarlo SIEMPRE del lado servidor (php, java, node, …)**
{% endhint %}

{% hint style="info" %}
Puedes generar tu API keys desde la sección Integraciones de tu cuenta de Perfit. Si tienes dudas sobre cómo hacerlo contáctanos a <soporte@myperfit.com>.
{% endhint %}


# Límites y errores

{% hint style="warning" %}
**SERVICIO DISCONTINUADO**

La API de envíos transaccionales ya no está disponible para nuevas suscripciones.&#x20;

Para las suscripciones activas, **el servicio será discontinuado en su totalidad el día 1/3/2026.**
{% endhint %}

## Límites

### Longitudes máximas

| Campo                      | Longitud máxima por elemento | Cantidad máxima |
| -------------------------- | ---------------------------- | --------------- |
| `subject`                  | 200 chars                    | -               |
| `recipients[]`             | -                            | 1000            |
| `cc[]`                     | -                            | 10              |
| `bcc[]`                    | -                            | 10              |
| `name` (from, to, cc, bcc) | 100 chars                    | -               |
| `headers[]`                | -                            | 10              |
| `headers[].key`            | 900 chars                    | -               |
| `headers[].value`          | 50 chars                     | -               |
| `tags[]`                   | 100 chars                    | 10              |
| `batch_code`               | 30 chars                     | -               |
| `content.html`             | 300KB                        |                 |
| `content.text`             | 300KB                        |                 |

### Tamaño total por request

El tamaño total de cada request no puede superar los 10MB.

### Tamaño de archivos adjuntos

Los archivos adjuntos se consideran como parte del mensaje, por lo que suman para el límite total de 10MB.

## Errores

Si ocurrió algo que impidió completar con éxito un pedido, la respuesta tiene esta forma:&#x20;

```javascript
{
    "success": false,
    "error": {
        "status": 403,
        "type": "forbidden",
        "message": "Origin IP address not allowed: 190.19.245.237"
    }
}
```

### Tipos de errores

El `type` puede ser alguno de estos:

| Status | error.type              | Descripción                                                                   |
| ------ | ----------------------- | ----------------------------------------------------------------------------- |
| 400    | `bad_request`           | El contenido del request tiene un formato inválido.                           |
| 400    | `validation_error`      | Algunos de los campos tienen contenido inválido.                              |
| 401    | `unauthorized`          | El API key utilizado es inválido o no se especificó.                          |
| 403    | `forbidden`             | El request fue bloqueado. La causa se especifica en message.                  |
| 500    | `internal_server_error` | Ocurrió un error inesperado en el servidor.                                   |
| 503    | `service_unavailable`   | El servicio no está disponible. Generalmente por límites temporales de envío. |

### Límite alcanzado

En caso de alcanzar el límite mensual, el pedido será rechazadao, indicando un error de tipo `503 service_unavailable` como este:

```javascript
{
    "success": false,
    "error": {
        "status": 503,
        "type": "service_unavailable",
        "message": "Sending limit reached: MONTHLY"
    }
}
```

### Errores de validación

Cuando `type` es `validation_error`, se incluye un objeto `errors` con todos los errores de validación encontrados:

```javascript
{
    "success": false,
    "error": {
        "status": 400,
        "type": "validation_error",
        "message": "Some fields have invalid values"
        "errors": {
            "from.email": "required"
        }
    }
}
```

Los tipos de error de validación pueden ser:

| Error                   | Descripción                                                              |
| ----------------------- | ------------------------------------------------------------------------ |
| `required`              | El campo es requerido.                                                   |
| `required_at_least_one` | Se debe incluir al menos un elemento en el campo de tipo array.          |
| `max_length_exceeded`   | Se excedió la longitud máximo en un campo de tipo string.                |
| `too_many_items`        | Se excedió la cantidad permitida de elementos en un campo de tipo array. |
| `invalid_value`         | El valor indicado tiene un formato inválido.                             |


# Formatos

{% hint style="warning" %}
**SERVICIO DISCONTINUADO**

La API de envíos transaccionales ya no está disponible para nuevas suscripciones.&#x20;

Para las suscripciones activas, **el servicio será discontinuado en su totalidad el día 1/3/2026.**
{% endhint %}

## Fechas

Todas las fechas deben indicarse en formato **ISO-8601**, por ejemplo: `2018-11-06T23:12:00Z` o`2018-11-06T23:12:00-03:00`

Los eventos se indican siempre usando el huso horario **UTC**.

## Content-Type

Se acepta únicamente contenidos de tipo `application/json`, debiendo siempre indicarse en el header `Content-Type`.

## Encoding y Charset

El encoding soportado para los requests es **UTF-8**. Todos los strings deben utilizar el charset **Unicode**.

## Direcciones de email

Todas las direcciones de email deben válidas, tal como se especifica en [RFC-5322](https://tools.ietf.org/html/rfc5322#section-3.4.1).


# Java SDK

Contamos con una librería para facilitar el uso de la API desde Java.

{% hint style="warning" %}
**SERVICIO DISCONTINUADO**

La API de envíos transaccionales ya no está disponible para nuevas suscripciones.&#x20;

Para las suscripciones activas, **el servicio será discontinuado en su totalidad el día 1/3/2026.**
{% endhint %}

## Instalación

Las formas más comunes de incluir la SDK en los proyectos es utilizando Maven o Gradle.&#x20;

### Maven

```markup
<dependency>
    <groupId>com.myperfit.sdk.transactional</groupId>
    <artifactId>transactionalsdk</artifactId>
    <version>[1.0,2.0)</version>
</dependency>
```

### Gradle

```javascript
compile group: 'com.myperfit.sdk.transactional', name: 'transactionalsdk', version: '1.+'
```

## Uso básico

```java
PerfitTransactional perfit = PerfitTransactional.builder()
        .apiKey("API_KEY")
        .build();

// Remitente
MailAddressRequest fromAddress = MailAddressRequest.builder()
        .email("from@midominio.com")
        .name("Nombre Remitente")
        .build();

// Contenidos
MailContentRequest content = MailContentRequest.builder()
        .html("<h1>contenido html</h1>")
        .text("contenido texto plan")
        .build();

// Listado de destinatarios
List<MailRecipientRequest> recipients = new ArrayList<>();

MailAddressRequest toAddress1 = MailAddressRequest.builder()
        .email("to@midominio.com")
        .name("Nombre Destinatario") 
        .build();
        
MailRecipientRequest recipient1 = MailRecipientRequest.builder()
        .to(toAddress1)
        .substitutions(Map.of("first_name","Nombre", "last_name", "Apellido"))
        .customArgs(Map.of("my_tracking_id","value1", "other_id", "value2"))
        .build();
        
recipients.add(recipient1);

// Mensaje completo
SendMailRequest request = SendMailRequest.builder()
        .from(fromAddress)
        .subject("Test Subject")
        .content(content)
        .recipients(recipients)
        .tags(List.of("tag1", "tag2"))
        .build();

try {
        // Envío del email
        perfit.send(request);
} catch (RequestFailedException ex) {
        // Manejar excepciones
}
```


# Ejemplos PHP y Node

Algunos ejemplos básicos para usar como referencia.

{% hint style="warning" %}
**SERVICIO DISCONTINUADO**

La API de envíos transaccionales ya no está disponible para nuevas suscripciones.&#x20;

Para las suscripciones activas, **el servicio será discontinuado en su totalidad el día 1/3/2026.**
{% endhint %}

## PHP

Ejemplo básico en PHP usando la libraría cURL

```php
$data = '{
    "from": { "email": "remitente@example.com" },
    "subject": "Asunto de prueba",
    "content": {"html": "<h1>Funciona!</h1>"},
    "recipients": [{"to": {"email": "recipient@example.com"}}]
}';

$ch = curl_init('https://transactional.myperfit.com/v1/mail/send');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    "Authorization: Bearer API_KEY",
    "Content-Type: application/json"
));

$result = curl_exec($ch);
```

## Node.js

Un ejemplo sencillo usando la libraría [axios](https://github.com/axios/axios).

```javascript
const axiosConfig = {
    headers: { 'Authorization': `Bearer ${transactionalApiKey}` }
}

const postData = {
    from: { email: 'remitente@example.com' },
    subject: 'Asunto de prueba',
    recipients: [
        { to: { email: 'recipient@example.com' } }
    ],
    content: { html: '<h1>Funciona!</h1>}
}

await axios.post('https://transactional.myperfit.com/v1/mail/send ', 
    postData, 
    axiosConfig);
```


# Envío usando SMTP

La API SMTP permite enviar emails utilizando clientes SMTP sin necesidad de cambiar implementaciones existentes. La funcionalidad es algo más limitada que la API HTTP.

{% hint style="warning" %}
**SERVICIO DISCONTINUADO**

La API de envíos transaccionales ya no está disponible para nuevas suscripciones.&#x20;

Para las suscripciones activas, **el servicio será discontinuado en su totalidad el día 1/3/2026.**
{% endhint %}

## Host y puertos

La conexión debe hacerse a **`smtp.myperfit.com`** utilizando el puerto: **`2525`**.

## Cifrado

En esta opción selecciona la opción **Ninguno** o **Sin cifrado**.

{% hint style="info" %}
Por el momento no soportamos opciones de cifrado como SSL o TLS.
{% endhint %}

## Autenticación

Debe utilizarse el método **`AUTH LOGIN`** para autenticarse con estas credenciales:

* username: "**apikey"**
* password: **MI\_API\_KEY**

**Por ejemplo:**

* username: "**apikey"**
* password: "**micuenta-tr-lf223iewndfc09wopijqesdqws"**

{% hint style="success" %}
Puedes **generar tu API key** desde la sección **Integraciones** en tu cuenta de Perfit.&#x20;

Si tienes dudas sobre cómo hacerlo contáctanos a <soporte@myperfit.com>.
{% endhint %}

## Limitaciones

La API SMTP por el momento cuenta con estas limitaciones:

* No es posible modificar las opciones de monitoreo. Por defecto está activado el monitoreo de aperturas y clicks.
* No es posible indicar modelos para utilizar en el motor de reemplazo (`substitutions`)
* No es posible indicar `tags`, `custom_args`, `batch_code` para identificar eventos o agrupar envíos.

Si necesitas utilizar algunas de estas características por favor utiliza la API HTTP.

## Ejemplo utilizando telnet

Las líneas maracadas con > son las que deben escribir.

```
> telnet smtp.myperfit.com 587
Trying 34.238.225.76...
Connected to smtp-transactional-prod-173515567.us-east-1.elb.amazonaws.com.
Escape character is '^]'.
220 localhost ESMTP Perfit v2

> EHLO minombre
250-smtp.myperfit.com
250-8BITMIME
250-SIZE 10000
250-AUTH LOGIN
250 Ok

> AUTH LOGIN
334 VXNlcm5hbWU6

> YXBpa2V5
334 UGFzc3dvcmQ6

> <<API KEY en base64>>
235 Authentication successful.

> MAIL FROM: yo@midominio.com
250 Ok

> RCPT TO: destinatario@domain.com
250 Ok

> DATA
354 End data with <CR><LF>.<CR><LF>
Subject: este es el asunto

Este es el contenido
.
250 Ok
```


# Contenidos dinámicos

Es posible personalizar cada email enviado utilizando la información disponible en los objetos substitution. Entre otras cosas, es posible indicar valores por defecto y bloques condicionales.

{% hint style="success" %}
Cuando se desea enviar una gran número de emails con un contenido similar, siempre que sea posible, es conveniente agrupar muchos `recipients` en un mismo POST y utilizar la personalización del contenido,  en vez de enviar un mensaje individial para cada uno.  **Esto mejor notablemente la performance y velocidad de entrega.**
{% endhint %}

## Personalización por destinatario

**El procesamiento del contenido se realiza por cada elemento `recipient`.** Esto implica que si para un mismo`recipient` se indican otros destintarios de tipo `cc` o `bcc`, estos recibirán **exactamente la misma copia del contenido.**&#x20;

Si se desea evitar este comportamiento, se deberán utilizar recipients independientes para cada destinatario.

## Motor de personalización

Utilizamos [Free Marker](https://freemarker.apache.org/) como motor de reemplazo, por lo que es posible utilizar todas las directivas y built-ins disponibles en su [documentación](https://freemarker.apache.org/docs/ref.html).

Es importante tener en cuenta que se debe utilizar la notación con corchetes para las directivas, por ejemplo:`[#if test] ... [/#if]`

Es posible utilizar los códigos de reemplazo en los siguientes elementos:

* `subject`
* `content.html`
* `content.text`
* `headers.value`

## Precedencia de modelos

Todos los objetos indicados dentro de `substitution`, tanto a nivel general como a nivel de `recipient`, pueden ser accedidos desde los contenidos.&#x20;

En caso de que un mismo objeto esté definido tanto a nivel de recipient como a nivel general, **tendrá siempre precedencia el definido a nivel de recipient**, es decir, el valor del `recipient` "pisa" al valor general.


# Links especiales

Algunas etiquetas son reemplazadas por links con funciones particulares.

## Versión online

Es posible incluir un link a la versión online del contenido html, su código es:

```
${urls.online_version}
```

Una forma de utilizarlo podria ser incluyendo un link en el encabezado:

```markup
<a href="${urls.online_version}">Versión online</a>
```

## Página de desuscripción

De forma similar, se puede incluir un link a la página de desuscripción, utilizando el código:

```
${urls.unsubscribe}
```

Por ejemplo:

```markup
<a href="${urls.unsubscribe}">Desuscribirme</a>
```


# Ejemplos

Algunos ejemplos de cómo es posible crear personalizaciones utilizando el motor de personalización.

## Valores por defecto

Una de las funciones más útiles es la posibilidad de indicar un valor por defecto para los casos en los que no exista o su valor sea vacío, por ejemplo:

`Hola ${contact.name!"amigo"}`

En cualquiera de los siguientes casos, se utilizaría el valor por defecto:

`{"contact": { "name": "" }}` , `{"contact": {} }` , `{ }`

## Bloque condicional

A veces se desea mostrar un bloque sólo si se cumplen ciertas condiciones, esto se puede resolver facilmente de esta forma:

```markup
[#if contact.gender == "M"]
  <!-- Contenido para hombres -->
[#else]
  <!-- Contenido para mujeres -->
[/#if]
```

```markup
[#if product.discount != 0]
  <!-- Producto con descuento -->
[#else]
  <!-- Producto sin descuento -->
[/#if]
```

## Iterador

Es posible recorrer arrays y armar el contenido en base a sus elementos, útil por ejemplo para mostrar productos de un carrito abandonado:

Siendo el modelo en `substitutions`:

```javascript
{
      "products": [ 
            { "title": "Producto 1", "price": "$123.50" },
            { "title": "Producto 2", "price": "$234.60" },            
            { "title": "Producto 2", "price": "$345.70" }            
      ]
}
```

Se puede iterar así:

```markup
[#list products as product]
  Producto: ${product.title}
  Precio: ${product.price}
[/#list]
```


# Configuración

Es posible controlar la forma en que se monitorea la actividad sobre cada email enviado.

## Identificación

{% hint style="info" %}
**Todos los eventos de monitoreo están asociados a un `mail_id` único por `recipient (to)`.**&#x20;
{% endhint %}

En el caso de que para un elemento `recipient` se incluyan `cc` o `bcc`, todos ellos compartirán el mismo `mail_id` y por ende cualquier acción que realicen (apertura, click, desuscripción, etc.) quedará asociada al mismo `mail_id`.&#x20;

Para evitar este comportamiento, se debería indicar cada uno como un `recipient` único.

## Activación

{% hint style="info" %}
**Por el momento** **sólo es posible configurar el monitoreo mediante la API HTTP**. En la API SMTP aún no está soportado.
{% endhint %}

La opciones de monitoreo se especifican dentro de la sección `tracking`. En caso de no especificarlas, se activarán por defecto tanto el monitoreo de aperturas como el de clicks.

```javascript
"tracking": { 
	"open": { 
		"enable": true 
	},
	"click": { 
		"enable": true
	},
	"ganalytics": {
		"utm_source": "Perfit",
		"utm_medium": "email",
		"utm_campaign": "my campaign",
		"utm_content": "",
		"utm_term": ""				
	}
}
```

{% hint style="info" %}
El monitreo de aperturas y clicks están sólo disponibles para el contenido de tipo `html`.
{% endhint %}

## Aperturas

Para el monitoreo de aperturas se incluye un tag `<img>` justo antes de cerrar el body del html, por lo que es necesario que en el cliente de email se visualicen las imágenes, en caso contrario no será contabilizada la apertura.

## Clicks

El monitoreo de los clicks se realiza reemplazando todos los enlaces (sólo los indicados en atributos `href`) por urls propias de Perfit que luego de contabilizar el click hacen un redirect al destino final.

### Intereses

Es posible indicar una serie de intereses asociados a un link. Estos intereses serán indicados junto a los eventos `track.mail.clicked`.&#x20;

La forma de asociar intereses mediante el atributo html `data-interests`. Es posible indicar más de un interés por link, separados por coma:

```markup
<a href="https://www.mydomain.com/prods/123" data-interests="electro,hogar">
    Link
</a>
```

### Desactivar monitoreo por link

Es posible desactivar el monitoreo de un link específico usando el atributo `data-track-disabled`.

```markup
<a href="https://www.mydomain.com/prods/123" data-track-disabled>
    Link no monitoreado
</a>
```

### Links variables

Existen casos en los que los links contienen partes variables, por ejemplo: `https://www.mydomain.com/prods/${product.id}`. El comportamiento por defecto es asociar los eventos de click a la URL original, antes del reemplazo.

Si lo que se desea es medir los clicks sobre cada URL final, es decir luego del reemplazo, es necesario usar el atributo `data-track-final-url`.

```markup
<a href="https://www.mydomain.com/prods/${product.id}" data-track-final-url>
    Link final monitoreado
</a>
```

## Google Analytics

Los parámetros utm indicados en `tracking.ganalytics` se incluirán en todos los links que se encuentren (sólo atributos `href`). Esto se hace independientemente de la activación del monitoreo de clicks.&#x20;

Sólo se incluyen en los links los parámetros utm que tengan un valor definido. En caso de que se encuentren links que ua incluyen alguno de los parámetros indicados, no se pisarán, conservando el valor original.

{% hint style="info" %}
Por el momento sólo es posible especificar los parámetros utm mediante la API HTTP. La API SMTP aún no soporta esta funcionalidad, y se deberán incluir los utm en forma individual en cada link.
{% endhint %}


# Webhooks de eventos

Puedes configurar webhooks HTTP para recibir notificaciones ante ciertos eventos, como las aperturas, clicks, desuscripciones y rebotes.

## Activación

Para activar el envío de webhooks, dentro de la aplicación de Perfit dirígite a la sección **Integraciones > Webhooks.** Haz click en **ACTIVAR** e ingresa la URL dónde se deben enviar los eventos.

{% hint style="info" %}
Por defecto se envían todos los tipos de eventos. En caso de necesitar recibir sólo algunos tipos de eventos en particular, puedes solicitarlo enviando un email a <dev@myperfit.com>.
{% endhint %}

## Tipos de eventos

### **Eventos de entrega**

* `track.mail.dropped`: El email fue descartado antes de intentar enviarlo. Puede deberse a un rebote o desuscripción previa.
* `track.mail.sent`: El email fue enviado. Esto no implica que haya sido entragado con éxito.
* `track.mail.bounced`: El email rebotó. Puede ser un rebote temporal (SOFT) o definitivo (HARD).

### **Eventos de actividad**

* `track.mail.opened`: Se detectó una apertura.
* `track.mail.first_opened`: Primera apertura sobre un envío (evento único por cada `mail_id`).
* `track.mail.clicked`: Se detectó un click sobre un link monitoreado.
* `track.mail.first_clicked`: Primer click sobre un envío (evento único por cada `mail_id`).
* `track.mail.unsubscribed`: El contacto se desuscribió o marcó el correo como spam.
* `track.mail.viewed_online`: Se producjo una visualización online.
* `track.mail.shared`: Se compartió el contenido utilizando el link de compartir en redes sociales.

## Notificaciones <a href="#notificacion" id="notificacion"></a>

Al generarse un evento para el cual existe un webhook configurado, se realizará un POST a la URL indicada. Se incluirá en el body del request un array con un conjunto de objetos, cada uno correspondiente a un evento único.

El procesamiento de los eventos deberá soportar recibir uno o varios eventos en un mismo POST. Los eventos agrupados pueden ser de distintos tipos.&#x20;

Por ejemplo:

```javascript
[
    {
        "timestamp": "2018-11-08T10:10:00Z",
        "track_id": "event_cjo4rwa3l0hqt0747awzl9jw2",
        "track_type": "track.mail.opened" , 
        ...
    },
    {
        "timestamp": "2018-11-08T10:10:02Z",
        "track_id": "event_cjoiasdf8uoieadscoiljadso",
        "track_type": "track.mail.clicked" , 
        ...
    },
    ...
]
```

### Respuesta y reintentos

Si el POST realizado recibe un código de respuesta distinto a 2xx, los eventos serás reencolados para su reintento. Se reintentará una vez más después de 1 minuto, luego los eventos serás descartados.

{% hint style="warning" %}
Si el volúmen de emails enviados genera muchos eventos, los webhooks pueden rápidamente sobrecargar al sevidor destino si no está configurado correctemente. Recomendamos utilizar loader.io para realizar pruebas de carga.
{% endhint %}

### Throttling

Para evitar sobrecargar al lado receptor, se limita la cantidad de requests por segundo a una misma URL a 250 requests/segundo.

## Detalles de los eventos

Todos los eventos incluyen cierta información básica. Además, dependiendo el tipo de evento, también se incluyen otros datos particulares.

### Información General

La información presente en todos los eventos es:

* `timestamp`: Fecha y hora de generación del evento.
* `sent_timestamp`: Fecha y hora de envío del email asociado al evento
* `hour_of_day`: Hora del día del `timestamp` (0-23).
* `day_of_week`: Día de la semana del `timestamp` (1-7).
* `track_id`: Id único del evento.
* `track_type`: Tipo de evento
* `batch_id`: Id asociado al batch. Si se indicó batch\_code en el request original tendra la forma: "nombrecuenta\_transactional\_batchcode"
* `mail_id`: Id asociado al email enviado.
* `mail_type`: Tipo de email, en este caso será siempre "transactional".
* `account`: Nombre de la cuenta.
* `email`: Dirección de email indicada en `recipient.to`
* `domain`: Dominio del email.
* `tags`: Array de etiquetas asociadas al batch, indicadas en el request de envío.
* `custom_args`: Objeto asociado al recipient, indicado en el request de envío.
* `mta`: Host utilizado para el envío del email.

### Eventos `track.mail.opened` y `track.mail.first_opened` <a href="#evento-track-mail-opened" id="evento-track-mail-opened"></a>

Además de la información básica, se incluyen también:&#x20;

* `user_agent`: string original del header User-Agent.
* `os_class`: Tipo de dispositivo: Mobile, Desktop, etc.
* `os`: Sistema operativo
* `os_version`: Sistema operativo y versión.
* `agent_name`: Nombre de cliente de correo
* `agent_version`:Nombre y versión de cliente de correo.
* `ip`: Dirección IP del cliente.
* `country`: País detectado a partir de la IP.
* `city`: Ciudad detectada a partir de la IP.
* `seconds_from_sent`: segundos transcurridos desde el evento track.mail.sent asociado a este evento. Sólo disponible en `track.mail.first_opened`.

Para los casos en que el pixel de trackeo de apertura se abra a través de un proxy (por ejemplo Gmail y Yahoo), no se incluye la información de geolocalización e identificación de dispositivo ya que no son confiables.

#### Ejemplo

```javascript
{
    "timestamp": "2018-11-05T20:43:34Z",
    "hour_of_day": 20,
    "day_of_week": 1
    
    "track_id": "event_cjo4rwa3l0hqt0747awzl9jw2",
    "track_type": "track.mail.opened",
​
    "batch_id": "micuenta_transactional_mibatchcode",
    "mail_id": "mail_cjo4qnh1242rl0833208jhofl",
    "mail_type": "transactional",
    "account": "micuenta",
    "email": "john@example.com",
    "domain": "example.com",
    
    "tags": [],
    "custom_args": {},
    
    "ip": "64.76.21.178",
    "country": "AR",
​
    "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
    "os_class": "Desktop",
    "os": "Windows NT",
    "os_version": "Windows 7",
    "agent_name_version": "Chrome 70",
    "agent_name": "Chrome"
}
```

### Evento `track.mail.clicked` y `track.mail.first_clicked` <a href="#evento-track-mail-clicked" id="evento-track-mail-clicked"></a>

Además de toda la información disponible en `track.mail.opened`, se incluye también:

* `url`: URL del link.
* `link_id`: Id único asociado al link.
* `interests`: Array de intereses si fueron indicados en el link.

#### Ejemplo

```javascript
{
    "timestamp": "2018-11-05T20:43:34Z",
    "hour_of_day": 20,
    "day_of_week": 1
    
    "track_id": "event_cjo4rwa3l0hqt0747awzl9jw2",
    "track_type": "track.mail.opened",
​
    "batch_id": "micuenta_transactional_mibatchcode",
    "mail_id": "mail_cjo4qnh1242rl0833208jhofl",
    "mail_type": "transactional",
    "account": "micuenta",
    "email": "john@example.com",
    "domain": "example.com",
    "contact_id": "276889",
    
    "tags": [],
    "custom_args": {},
    
    "ip": "64.76.21.178",
    "country": "AR",
    "city": "",
​
    "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
    "os_class": "Desktop",
    "os": "Windows NT",
    "os_version": "Windows 7",
    "agent_name_version": "Chrome 70",
    "agent_name": "Chrome"
    
    "url": "",
    "link_id": "",
    "interests": []        
}
```

​Para el caso de `track.mail.clicked`, siempre se tendrá disponible la información de geolocalización y dispositivo.

### Evento `track.mail.unsubscribed`

Además de la información general, se incluye también:

* `reason`: Código asociado a la razón de desuscripción. Los valores posibles son:
  * not\_interested
  * too\_frequent
  * spam\_never\_subscribed
  * spam\_offensive
  * spam\_fbl
  * oneclick

### Evento `track.mail.bounced`

Además de la información general, se incluye también:

* `bounce_type`: Tipo de rebote, puede ser hard o soft.
* `message`: Primeras líneas del mensaje de rebote recibido.

### Evento `track.mail.sent`

No se incluye ninguna información adicional además de la general.

### Evento `track.mail.viewed_online`

No se incluye ninguna información adicional además de la general.

### Evento `track.mail.dropped`

No se incluye ninguna información adicional además de la general.

### Evento `track.mail.shared`

Además de toda la información disponible en `track.mail.opened`, se incluye también:

* `shared_on`: red social en la que el contacto compartió el contenido


# Listado de actividad

Es posible obtener el historial de actividad de todos los envíos de la cuenta. Pueden obtener los eventos de cada mail enviado, apertura, click, desuscripción, rebote, etc.

{% hint style="info" %}
La autenticación a utilizar en este recurso es la misma que se utiliza en [Contacts API](/contacts-api/autenticacion). Para obtener el API key de un usuario puedes revisar este artículo.
{% endhint %}

## /activity

<mark style="color:blue;">`GET`</mark> `https://api.myperfit.com/v2/:account/activity`

Devuelve un listado de los eventos utilizando los filtros indicados. La estructura de los objetos varía dependiendo del tipo de evento, como se indica en la sección Webhooks.

#### Path Parameters

| Name    | Type   | Description      |
| ------- | ------ | ---------------- |
| account | string | Nombre de cuenta |

#### Query Parameters

| Name                    | Type    | Description                                                                                                                                             |
| ----------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| filters.track\_type     | string  | Filtrado por tipo de evento, ej:  `track.mail.sent`, `track.mail.opened`, `track.mail.clicked`, ... Dejar vacío para recibir todo los tipos de eventos. |
| filters.batch\_id       | string  | Filtrado por batch de envío, ej: `myaccount_bulk_123`                                                                                                   |
| view                    | string  | Formato de salida: `full`(toda la info disponible), `default`(info básica) o `simple`(sólo email).                                                      |
| filters.timestamp.gtrel | string  | Fecha relativa de inicio de datos. Ej: `now-1h`, `now-5d`                                                                                               |
| filters.timestamp.gt    | boolean | Fecha absulta de inicio de datos. No puede indicarse en conjunto con gtrel. Ej: `2019-10-17T03:49:14Z`                                                  |
| q                       | string  | Dirección de email.                                                                                                                                     |

#### Headers

| Name           | Type   | Description                 |
| -------------- | ------ | --------------------------- |
| Authentication | string | API Key de cuenta en Perfit |

{% tabs %}
{% tab title="200 " %}

```javascript
{
    "paging": {
        "next": "",
        "total": 2,
        "results": 2
    },
    "data": [
        { ... },    
        { ... }        
    ],
}
```

{% endtab %}
{% endtabs %}

### Paginado

Para obtener las sucesivas páginas, se debe utilizar el link que aparece en `paging.next`. Si su contenido está vació significa que esa es la última págnia.


