# Elabora le email in entrata con i webhook

> Iscrivi un webhook a email.received, verifica ogni richiesta, poi recupera con l’API corpo, header e allegati di ogni messaggio ricevuto.

Questa guida mostra come reagire alle email ricevute nel tuo codice. Emailit notifica il tuo endpoint con un evento `email.received`, e il tuo codice recupera il messaggio completo con l’API. Gli esempi verificano la firma, gestiscono i batch di eventi, scaricano gli allegati e saltano i duplicati.

## Prima di iniziare

- Le [email in entrata sono configurate](/it/docs/inbound/set-up/) e un messaggio di prova compare nella scheda **Incoming**.
- Una chiave API con **Full Access**. Le chiavi Sending Only non possono leggere il contenuto delle email. Vedi [Chiavi API](/it/docs/developers/api-keys/).
- Un endpoint HTTPS pubblico che accetti richieste `POST`. Per lo sviluppo in locale, usa un tunnel come ngrok o Cloudflare Tunnel.

## Come funziona il flusso

Il webhook ti dice che è arrivato un messaggio. Non contiene il corpo né gli allegati: così le richieste restano leggere e recuperi il contenuto solo quando ti serve.

```json title="email.received (un evento nell’array della richiesta)"
{
  "event_id": "evt_2xGk9Tb4QmF6wN2cJpR7uZsE1kD",
  "type": "email.received",
  "object": {
    "id": "em_2xGk9Rz2NcV7bL4pWqH8sYdT5fA",
    "object": "email",
    "from": "ada@example.com",
    "to": "support@inbound.acme.com",
    "subject": "Question about order 1042",
    "created_at": "2026-10-01T09:14:05.317000+00:00"
  },
  "data": {
    "object": {
      "id": "em_2xGk9Rz2NcV7bL4pWqH8sYdT5fA",
      "object": "email",
      "from": "ada@example.com",
      "to": "support@inbound.acme.com",
      "subject": "Question about order 1042",
      "created_at": "2026-10-01T09:14:05.317000+00:00"
    }
  }
}
```

Usa `data.object.id` con [Recupera un’email](/it/docs/api-reference/emails/get/) per ottenere il messaggio analizzato:

```json title="GET /v2/emails/em_2xGk9Rz2NcV7bL4pWqH8sYdT5fA (abbreviato)"
{
  "object": "email",
  "id": "em_2xGk9Rz2NcV7bL4pWqH8sYdT5fA",
  "type": "inbound",
  "status": "received",
  "from": "ada@example.com",
  "to": "support@inbound.acme.com",
  "subject": "Question about order 1042",
  "message_id": "<CAF3x1@mail.example.com>",
  "headers": {
    "From": "Ada Lovelace <ada@example.com>",
    "Reply-To": "ada@example.com",
    "Date": "Thu, 01 Oct 2026 09:14:02 +0000"
  },
  "body": {
    "text": "Hi, my order hasn't arrived yet...",
    "html": "<p>Hi, my order hasn't arrived yet...</p>"
  },
  "attachments": [
    {
      "filename": "receipt.pdf",
      "content_type": "application/pdf",
      "size": 48213,
      "content_id": null,
      "content_disposition": "attachment",
      "content": "JVBERi0xLjcKJcfsj6IK..."
    }
  ]
}
```

Il `content` degli allegati è codificato in Base64. I nomi degli header mantengono le maiuscole e minuscole originali, e quando un header compare più volte (come `Received`) viene mantenuto solo l’ultimo valore. Se ti serve solo una parte del messaggio, usa gli endpoint più specifici: [Recupera il corpo](/it/docs/api-reference/emails/body/), [Elenca gli allegati](/it/docs/api-reference/emails/attachments/), oppure [Recupera il MIME grezzo](/it/docs/api-reference/emails/raw/) per il sorgente originale.

## Crea il webhook

**Pannello**

  1. **Aggiungi il webhook.** Vai a **Email API → Webhooks**, seleziona **Add webhook**, inserisci un nome e l’URL del tuo endpoint e seleziona **Create**.

  2. **Copia il secret.** La finestra mostra il secret del webhook (`whsec_…`) una sola volta. Memorizzalo come `EMAILIT_WEBHOOK_SECRET` nell’ambiente della tua app.

  3. **Iscriviti solo a email.received.** Nella scheda **Settings** del webhook, disattiva **All events**, seleziona `email.received` in **Emails** e seleziona **Save**.

**API**

  Chiama [Crea un webhook](/it/docs/api-reference/webhooks/create/) con l’elenco degli eventi. La risposta include il `secret`.

```bash
curl https://api.emailit.com/v2/webhooks \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Inbound mail",
    "url": "https://acme.com/webhooks/emailit",
    "events": ["email.received"]
  }'
```

Nei piani Pro, Business e Custom puoi aggiungere un [filtro sul payload](/it/docs/webhooks/set-up/#filter-events-by-payload), così il webhook riceve solo la posta per alcuni indirizzi, ad esempio quelli in cui `to` termina con `@inbound.acme.com`.

## Scrivi il gestore

Il corpo di ogni richiesta è un array JSON di massimo 100 eventi. Il gestore qui sotto verifica la [firma](/it/docs/webhooks/request-signature/), scorre l’array, salta tutto ciò che non è `email.received` o che è già stato elaborato e recupera ogni messaggio.

**Node.js**

```javascript title="server.js"
import crypto from 'node:crypto';
import express from 'express';

const app = express();
const WEBHOOK_SECRET = process.env.EMAILIT_WEBHOOK_SECRET; // whsec_...
const API_KEY = process.env.EMAILIT_API_KEY; // secret_..., Full Access
const processed = new Set(); // use a database table with a unique key in production

function verify(rawBody, signature, timestamp) {
  if (!signature || !timestamp) return false;
  const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
  if (!(age <= 300)) return false; // 5-minute tolerance
  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

async function fetchEmail(id) {
  const res = await fetch(`https://api.emailit.com/v2/emails/${id}`, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });
  if (!res.ok) throw new Error(`Emailit API returned ${res.status}`);
  return res.json();
}

async function handleReceived(event) {
  const email = await fetchEmail(event.data.object.id);
  console.log(`From ${email.from} to ${email.to}: ${email.subject}`);
  console.log(email.body.text ?? email.body.html);

  for (const file of email.attachments ?? []) {
    const bytes = Buffer.from(file.content, 'base64');
    console.log(`Attachment ${file.filename} (${file.content_type}, ${bytes.length} bytes)`);
  }
}

// Keep the raw body: the signature is computed over the exact bytes.
app.post('/webhooks/emailit', express.raw({ type: 'application/json' }), async (req, res) => {
  if (!verify(req.body, req.get('X-Emailit-Signature'), req.get('X-Emailit-Timestamp'))) {
    return res.status(401).send('Invalid signature');
  }

  const events = JSON.parse(req.body.toString('utf8'));
  try {
    for (const event of events) {
      if (event.type !== 'email.received' || processed.has(event.event_id)) continue;
      await handleReceived(event);
      processed.add(event.event_id);
    }
    res.sendStatus(200);
  } catch (err) {
    console.error(err);
    res.sendStatus(500); // Emailit retries the whole batch later
  }
});

app.listen(3000);
```

**Python**

```python title="app.py"
import base64
import hashlib
import hmac
import json
import os
import time

import requests
from flask import Flask, abort, request

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["EMAILIT_WEBHOOK_SECRET"].encode()  # whsec_...
API_KEY = os.environ["EMAILIT_API_KEY"]  # secret_..., Full Access
processed = set()  # use a database table with a unique key in production

def verify(raw_body: bytes, signature: str | None, timestamp: str | None) -> bool:
    if not signature or not timestamp:
        return False
    try:
        if abs(time.time() - int(timestamp)) > 300:  # 5-minute tolerance
            return False
    except ValueError:
        return False
    signed = timestamp.encode() + b"." + raw_body
    expected = hmac.new(WEBHOOK_SECRET, signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

def fetch_email(email_id: str) -> dict:
    response = requests.get(
        f"https://api.emailit.com/v2/emails/{email_id}",
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=10,
    )
    response.raise_for_status()
    return response.json()

def handle_received(event: dict) -> None:
    email = fetch_email(event["data"]["object"]["id"])
    print(f"From {email['from']} to {email['to']}: {email['subject']}")
    print(email["body"]["text"] or email["body"]["html"])

    for attachment in email.get("attachments") or []:
        data = base64.b64decode(attachment["content"])
        print(f"Attachment {attachment['filename']} ({len(data)} bytes)")

@app.post("/webhooks/emailit")
def emailit_webhook():
    raw_body = request.get_data()  # raw bytes, before any JSON parsing
    if not verify(
        raw_body,
        request.headers.get("X-Emailit-Signature"),
        request.headers.get("X-Emailit-Timestamp"),
    ):
        abort(401)

    for event in json.loads(raw_body):
        if event["type"] != "email.received" or event["event_id"] in processed:
            continue
        handle_received(event)  # an exception returns 500 and Emailit retries
        processed.add(event["event_id"])

    return "", 200
```

**PHP**

```php title="webhook.php"
<?php
$webhookSecret = getenv('EMAILIT_WEBHOOK_SECRET'); // whsec_...
$apiKey = getenv('EMAILIT_API_KEY'); // secret_..., Full Access

// Dedupe table: CREATE TABLE processed_events (event_id TEXT PRIMARY KEY)
$db = new PDO('sqlite:' . __DIR__ . '/events.sqlite');

$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_EMAILIT_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_EMAILIT_TIMESTAMP'] ?? '';

$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $webhookSecret);
$fresh = ctype_digit($timestamp) && abs(time() - (int) $timestamp) <= 300;
if (!$fresh || !hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

function fetchEmail(string $id, string $apiKey): array
{
    $ch = curl_init('https://api.emailit.com/v2/emails/' . rawurlencode($id));
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ["Authorization: Bearer {$apiKey}"],
        CURLOPT_TIMEOUT => 10,
    ]);
    $body = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    if ($status !== 200) {
        throw new RuntimeException("Emailit API returned {$status}");
    }
    return json_decode($body, true);
}

try {
    foreach (json_decode($rawBody, true) as $event) {
        if ($event['type'] !== 'email.received') {
            continue;
        }
        $seen = $db->prepare('SELECT 1 FROM processed_events WHERE event_id = ?');
        $seen->execute([$event['event_id']]);
        if ($seen->fetchColumn()) {
            continue;
        }

        $email = fetchEmail($event['data']['object']['id'], $apiKey);
        error_log("From {$email['from']} to {$email['to']}: {$email['subject']}");
        foreach ($email['attachments'] ?? [] as $attachment) {
            $bytes = base64_decode($attachment['content']);
            file_put_contents(sys_get_temp_dir() . '/' . basename($attachment['filename']), $bytes);
        }

        $db->prepare('INSERT INTO processed_events (event_id) VALUES (?)')
           ->execute([$event['event_id']]);
    }
    http_response_code(200);
} catch (Throwable $e) {
    error_log($e->getMessage());
    http_response_code(500); // Emailit retries the whole batch later
}
```

## Gestisci nuovi tentativi e duplicati

- **Rispondi entro 30 secondi.** Emailit attende una risposta `2xx` per un massimo di 30 secondi. Un timeout, uno stato diverso da `2xx` o un reindirizzamento contano come errore, e l’intero batch viene ritentato secondo il [calendario dei nuovi tentativi](/it/docs/webhooks/retries-and-failures/). Se il recupero e l’elaborazione possono richiedere più tempo, memorizza gli eventi in una coda, restituisci `200` ed elaborali in un job in background.
- **Elimina i duplicati in base a `event_id`.** Un batch ritentato contiene di nuovo tutti i suoi eventi, compresi quelli che avevi già gestito prima dell’errore. Registra ogni `event_id` dopo l’elaborazione e salta gli ID già visti. Ogni messaggio ricevuto ha anche un proprio ID email, che puoi usare come seconda chiave.
- **Conferma anche gli eventi che non gestisci.** Restituisci `2xx` anche quando un batch contiene solo tipi di evento che ignori. Altrimenti Emailit continua a ritentarli.
- **Recupera il contenuto subito.** Il contenuto dei messaggi viene conservato per un periodo limitato che dipende dal piano (7 giorni con Pay as you go). Dopo, l’API restituisce l’email senza corpo né allegati. Vedi [Conservazione dei dati](/it/docs/data-retention/).

## Instrada i messaggi in base all’indirizzo

Poiché ogni parte locale viene accettata, puoi codificare informazioni nell’indirizzo e rileggerle da `to`. Ad esempio, invia le notifiche con `reply_to` impostato su `reply+4821@inbound.acme.com`, poi instrada le risposte al ticket 4821:

```javascript
const match = email.to.match(/^reply\+(\d+)@inbound\.acme\.com$/i);
if (match) {
  await addReplyToTicket(match[1], email.body.text);
}
```

## Verifica che funzioni

1. Invia un messaggio a un indirizzo del sottodominio di ricezione.
2. Nella scheda **Requests** del webhook, la richiesta `email.received` risulta **Delivered**.
3. La tua applicazione registra nei log mittente, oggetto ed eventuali allegati.

Se la richiesta risulta **Attempting** o **Failed**, seleziona **View** su una riga non riuscita per vedere il codice di stato e il corpo della risposta restituiti dal tuo endpoint. Vedi [Nuovi tentativi ed errori](/it/docs/webhooks/retries-and-failures/).

## Vedi anche

  - [Verifica le firme dei webhook](/it/docs/webhooks/request-signature/)
  - [Richieste webhook](/it/docs/webhooks/webhook-requests/)
  - [Inoltra con le automazioni](/it/docs/inbound/forward-with-automations/)
  - [Recupera un’email](/it/docs/api-reference/emails/get/)

---
Fonte: https://emailit.com/it/docs/inbound/process-with-webhooks/
