> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# Raccolta e analisi dei dati con Agents Platform in Next.js

> **Note**
>
> **Tutorial** · Presuppone che tu abbia completato la [guida rapida di ElevenAgents ](/docs/it/eleven-agents/quickstart) e configurato un progetto Next.js.

## Introduzione

In questo tutorial imparerai a creare un agente vocale che raccoglie informazioni dall'utente tramite la conversazione, quindi analizza ed estrae i dati in modo strutturato e li invia alla tua applicazione tramite il webhook post-chiamata.

## Requisiti

* Un account ElevenLabs con una [chiave API](https://elevenlabs.io/app/settings/api-keys).
* Node.js v18 o versione successiva installato sul tuo computer.

## Configurazione

### Crea un nuovo progetto Next.js

Ti consigliamo di usare il nostro [template Agents Platform per v0.dev](https://v0.dev/community/nextjs-5TN93pl3bRS) come punto di partenza per la tua applicazione. Questo template è un'applicazione Next.js pronta per la produzione con l'agente ElevenLabs già integrato.

### Configura Agents Platform

Segui la nostra [guida a Next.js](/docs/it/eleven-agents/guides/quickstarts/next-js) per i passaggi di installazione e configurazione. Poi torna qui per sviluppare le funzionalità avanzate.

## Configurazione dell'agente

#### Accedi a ElevenLabs

Vai su [elevenlabs.io](https://elevenlabs.io/app/sign-up) e accedi al tuo account.

#### Crea un nuovo agente

Vai a [Agents Platform > Agents](https://elevenlabs.io/app/agents/agents) e
crea un nuovo agente dal template vuoto.

#### Imposta il primo messaggio

Imposta il primo messaggio e specifica la variabile dinamica per la piattaforma.

```txt
Hi {{user_name}}, I'm Jess from the ElevenLabs team. I'm here to help you design your very own ElevenLabs agent! To kick things off, let me know what kind of agent you're looking to create. For example, do you want a support agent, to help your users answer questions, or a sales agent to sell your products, or just a friend to chat with?
```

#### Imposta il prompt di sistema

Imposta il prompt di sistema. Puoi includere anche qui le variabili dinamiche.

```txt
You are Jess, a helpful agent helping {{user_name}} to design their very own ElevenLabs agent. The design process involves the following steps:

"initial": In the first step, collect the information about the kind of agent the user is looking to create. Summarize the user's needs back to them and ask if they are ready to continue to the next step. Only once they confirm proceed to the next step.
"training": Tell the user to create the agent's knowledge base by uploading documents, or submitting URLs to public websites with information that should be available to the agent. Wait patiently without talking to the user. Only when the user confirms that they've provided everything then proceed to the next step.
"voice": Tell the user to describe the voice they want their agent to have. For example: "A professional, strong spoken female voice with a slight British accent." Repeat the description of their voice back to them and ask if they are ready to continue to the next step. Only once they confirm proceed to the next step.
"email": Tell the user that we've collected all necessary information to create their ElevenLabs agent and ask them to provide their email address to get notified when the agent is ready.

Always call the `set_ui_state` tool when moving between steps!
```

#### Configura gli strumenti client

Configura il seguente strumento client per navigare tra i passaggi:

* Nome: `set_ui_state`
  * Descrizione: Usa questo strumento lato client per navigare tra i diversi stati dell'interfaccia utente.
  * Attendi risposta: `true`
  * Timeout della risposta (secondi): 1
  * Parametri:
    * Tipo di dati: string
    * Identificatore: step
    * Obbligatorio: true
    * Tipo di valore: LLM Prompt
    * Descrizione: Il passaggio a cui passare nell'interfaccia utente. Usa esclusivamente i passaggi definiti nel prompt di sistema.

#### Imposta la voce dell'agente

Vai alla scheda `Voice` e imposta la voce del tuo agente. Puoi trovare un elenco di voci consigliate per Agents Platform nella [documentazione sulla progettazione di voci conversazionali](/docs/it/eleven-agents/customization/voice/best-practices/conversational-voice-design#voices).

#### Imposta i criteri di valutazione

Vai alla scheda `Analysis` e aggiungi un nuovo criterio di valutazione.

* Nome: `all_data_provided`
  * Prompt: Valuta se l'utente ha fornito una descrizione dell'agente che desidera generare e una descrizione della voce che l'agente dovrebbe avere.

#### Configura la raccolta dati

Puoi usare l'analisi post-chiamata per estrarre dati dalla conversazione. Nella scheda `Analysis`, in `Data Collection`, aggiungi i seguenti elementi:

* Identificatore: `voice_description`
  * `data-type`: `String`
  * Descrizione: In base alla descrizione della voce che l'utente desidera per l'agente, genera una descrizione concisa della voce, includendo età, accento, tono e carattere, se disponibili.
* Identificatore: `agent_description`
  * `data-type`: `String`
  * Descrizione: In base alla descrizione dell'agente che l'utente desidera progettare, genera un prompt utilizzabile per addestrare un modello ad agire come l'agente.

#### Configura il webhook post-chiamata

I [webhook post-chiamata](https://elevenlabs.io/docs/eleven-agents/workflows/post-call-webhooks) vengono usati per avvisarti quando una chiamata termina e i passaggi di analisi ed estrazione dei dati sono stati completati.

In questo esempio, il webhook post-chiamata esegue alcuni passaggi:

1. Crea un design vocale personalizzato in base a `voice_description`.
2. Crea un agente ElevenLabs per gli utenti in base a `agent_description` fornito.
3. Recupera i documenti della knowledge base dallo stato della conversazione archiviato in Redis e associa la knowledge base all'agente.
4. Invia un'email all'utente per informarlo che il suo agente ElevenLabs personalizzato è pronto a conversare.

Quando esegui l'app in locale, ti serve uno strumento come [ngrok](https://ngrok.com/) per esporre il server locale a Internet.

```bash
ngrok http 3000
```

Vai alle [impostazioni di Agents Platform](https://elevenlabs.io/app/agents/settings) e, in `Post-Call Webhook`, crea un nuovo webhook e incolla il tuo URL ngrok: `https://<your-url>.ngrok-free.app/api/convai-webhook`.

Dopo aver salvato il webhook, riceverai un segreto per i webhook. Assicurati di conservarlo in modo sicuro, poiché dovrai impostarlo in seguito nel file `.env`.

## Integra le funzionalità avanzate

### Configura un database Redis per archiviare lo stato della conversazione

In questo esempio usiamo Redis per archiviare lo stato della conversazione. Questo ci consente di recuperare i documenti della knowledge base dallo stato della conversazione al termine della chiamata.

Se esegui il deployment su Vercel, puoi configurare l'integrazione [Upstash for Redis](https://vercel.com/marketplace/upstash) oppure registrarti a un [account Upstash](https://upstash.com/) gratuito e creare un nuovo database.

### Configura Resend per inviare email post-chiamata

In questo esempio usiamo Resend per inviare all'utente l'email post-chiamata. Per farlo, devi creare un [account Resend](https://resend.com/) gratuito e configurare una nuova chiave API.

### Imposta le variabili d'ambiente

Nella root del progetto, crea un file `.env` e aggiungi le seguenti variabili:

```bash
ELEVENLABS_CONVAI_WEBHOOK_SECRET=
ELEVENLABS_API_KEY=
ELEVENLABS_AGENT_ID=

# Resend
RESEND_API_KEY=
RESEND_FROM_EMAIL=

# Upstash Redis
KV_URL=
KV_REST_API_READ_ONLY_TOKEN=
REDIS_URL=
KV_REST_API_TOKEN=
KV_REST_API_URL=
```

### Configura sicurezza e autenticazione

Per proteggere il tuo agente ElevenLabs, devi abilitare l'autenticazione nella scheda `Security` della configurazione dell'agente.

Una volta abilitata l'autenticazione, per avviare una conversazione con l'agente devi creare un URL firmato in un ambiente server-side sicuro. In Next.js, puoi farlo configurando una nuova route API.

**`./app/api/signed-url/route.ts`**

```tsx ./app/api/signed-url/route.ts
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { NextResponse } from "next/server";

export async function GET() {
  const agentId = process.env.ELEVENLABS_AGENT_ID;
  if (!agentId) {
    throw Error("ELEVENLABS_AGENT_ID is not set");
  }
  try {
    const elevenlabs = new ElevenLabsClient();
    const response = await elevenlabs.conversationalAi.conversations.getSignedUrl({
      agentId,
    });
    return NextResponse.json({ signedUrl: response.signedUrl });
  } catch (error) {
    console.error("Error:", error);
    return NextResponse.json({ error: "Failed to get signed URL" }, { status: 500 });
  }
}
```

### Avvia la sessione di conversazione

Per avviare la conversazione, chiama prima la route API per ottenere l'URL firmato, quindi usa l'hook `useConversation` per configurare la sessione di conversazione.

**`./page.tsx`**

```tsx ./page.tsx {1,4,20-25,31-46}
import { useConversation } from "@elevenlabs/react";

async function getSignedUrl(): Promise<string> {
  const response = await fetch("/api/signed-url");
  if (!response.ok) {
    throw Error("Failed to get signed url");
  }
  const data = await response.json();
  return data.signedUrl;
}

export default function Home() {
  // ...
  const [currentStep, setCurrentStep] = useState<
    "initial" | "training" | "voice" | "email" | "ready"
  >("initial");
  const [conversationId, setConversationId] = useState("");
  const [userName, setUserName] = useState("");

  const conversation = useConversation({
    onConnect: () => console.log("Connected"),
    onDisconnect: () => console.log("Disconnected"),
    onMessage: (message: string) => console.log("Message:", message),
    onError: (error: Error) => console.error("Error:", error),
  });

  const startConversation = useCallback(async () => {
    try {
      // Request microphone permission
      await navigator.mediaDevices.getUserMedia({ audio: true });
      // Start the conversation with your agent
      const signedUrl = await getSignedUrl();
      const convId = await conversation.startSession({
        signedUrl,
        dynamicVariables: {
          user_name: userName,
        },
        clientTools: {
          set_ui_state: ({ step }: { step: string }): string => {
            // Allow agent to navigate the UI.
            setCurrentStep(step as "initial" | "training" | "voice" | "email" | "ready");
            return `Navigated to ${step}`;
          },
        },
      });
      setConversationId(convId);
      console.log("Conversation ID:", convId);
    } catch (error) {
      console.error("Failed to start conversation:", error);
    }
  }, [conversation, userName]);
  const stopConversation = useCallback(async () => {
    await conversation.endSession();
  }, [conversation]);
  // ...
}
```

### Strumento client e variabili dinamiche

Nella configurazione dell'agente precedente, hai registrato lo strumento client `set_ui_state` per consentire all'agente di navigare tra i diversi stati dell'interfaccia utente. Per completare l'integrazione, devi passare l'implementazione dello strumento client alle opzioni di `conversation.startSession`.

Qui puoi anche passare le variabili dinamiche alla conversazione.

**`./page.tsx`**

```tsx ./page.tsx {3-5,7-11}
const convId = await conversation.startSession({
  signedUrl,
  dynamicVariables: {
    user_name: userName,
  },
  clientTools: {
    set_ui_state: ({ step }: { step: string }): string => {
      // Allow agent to navigate the UI.
      setCurrentStep(step as "initial" | "training" | "voice" | "email" | "ready");
      return `Navigated to ${step}`;
    },
  },
});
```

### Caricamento di documenti nella knowledge base

Nel passaggio `Training`, l'agente chiederà all'utente di caricare documenti o inviare URL di siti web pubblici contenenti informazioni che dovrebbero essere disponibili al proprio agente. Qui puoi usare la nuova funzione `after` di [Next.js 15](https://nextjs.org/docs/app/api-reference/functions/after) per consentire il caricamento dei documenti in background.

Crea una nuova server action `upload` per gestire la creazione della knowledge base all'invio del modulo. Una volta creati tutti i documenti della knowledge base, archivia l'ID della conversazione e gli ID della knowledge base nel database Redis.

**`./app/actions/upload.ts`**

```tsx ./app/actions/upload.ts {26,32,44,56-60}
"use server";

import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { Redis } from "@upstash/redis";
import { redirect } from "next/navigation";
import { after } from "next/server";

// Initialize Redis
const redis = Redis.fromEnv();

const elevenlabs = new ElevenLabsClient({
  apiKey: process.env.ELEVENLABS_API_KEY,
});

export async function uploadFormData(formData: FormData) {
  const knowledgeBase: Array<{
    id: string;
    type: "file" | "url";
    name: string;
  }> = [];
  const files = formData.getAll("file-upload") as File[];
  const email = formData.get("email-input");
  const urls = formData.getAll("url-input");
  const conversationId = formData.get("conversation-id");

  after(async () => {
    // Upload files as background job
    // Create knowledge base entries
    // Loop through files and create knowledge base entries
    for (const file of files) {
      if (file.size > 0) {
        const response = await elevenlabs.conversationalAi.knowledgeBase.documents.createFromFile({
          file,
        });
        if (response.id) {
          knowledgeBase.push({
            id: response.id,
            type: "file",
            name: file.name,
          });
        }
      }
    }
    // Append all urls
    for (const url of urls) {
      const response = await elevenlabs.conversationalAi.knowledgeBase.documents.createFromUrl({
        url: url as string,
      });
      if (response.id) {
        knowledgeBase.push({
          id: response.id,
          type: "url",
          name: `url for ${conversationId}`,
        });
      }
    }

    // Store knowledge base IDs and conversation ID in database.
    const redisRes = await redis.set(
      conversationId as string,
      JSON.stringify({ email, knowledgeBase })
    );
    console.log({ redisRes });
  });

  redirect("/success");
}
```

## Gestione del webhook post-chiamata

Il [webhook post-chiamata](/docs/it/eleven-agents/workflows/post-call-webhooks) viene attivato quando una chiamata termina e i passaggi di analisi ed estrazione dei dati sono stati completati.

Qui vengono eseguiti alcuni passaggi:

1. Verifica il segreto del webhook e costruisce il payload del webhook.
2. Crea un design vocale personalizzato in base a `voice_description`.
3. Crea un agente ElevenLabs per gli utenti in base a `agent_description` fornito.
4. Recupera i documenti della knowledge base dallo stato della conversazione archiviato in Redis e associa la knowledge base all'agente.
5. Invia un'email all'utente per informarlo che il suo agente ElevenLabs personalizzato è pronto a conversare.

**`./app/api/convai-webhook/route.ts`**

```ts ./app/api/convai-webhook/route.ts
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import { Redis } from "@upstash/redis";
import crypto from "crypto";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { Resend } from "resend";

import { EmailTemplate } from "@/components/email/post-call-webhook-email";

// Initialize Redis
const redis = Redis.fromEnv();
// Initialize Resend
const resend = new Resend(process.env.RESEND_API_KEY);

const elevenlabs = new ElevenLabsClient({
  apiKey: process.env.ELEVENLABS_API_KEY,
});

export async function GET() {
  return NextResponse.json({ status: "webhook listening" }, { status: 200 });
}

export async function POST(req: NextRequest) {
  const secret = process.env.ELEVENLABS_CONVAI_WEBHOOK_SECRET; // Add this to your env variables
  const { event, error } = await constructWebhookEvent(req, secret);
  if (error) {
    return NextResponse.json({ error: error }, { status: 401 });
  }

  if (event.type === "post_call_transcription") {
    const { conversation_id, analysis, agent_id } = event.data;

    if (
      agent_id === process.env.ELEVENLABS_AGENT_ID &&
      analysis.evaluation_criteria_results.all_data_provided?.result === "success" &&
      analysis.data_collection_results.voice_description?.value
    ) {
      try {
        // Design the voice
        const voicePreview = await elevenlabs.textToVoice.createPreviews({
          voiceDescription: analysis.data_collection_results.voice_description.value,
          text: "The night air carried whispers of betrayal, thick as London fog. I adjusted my cufflinks - after all, even spies must maintain appearances, especially when the game is afoot.",
        });
        const voice = await elevenlabs.textToVoice.createVoiceFromPreview({
          voiceName: `voice-${conversation_id}`,
          voiceDescription: `Voice for ${conversation_id}`,
          generatedVoiceId: voicePreview.previews[0].generatedVoiceId,
        });

        // Get the knowledge base from redis
        const redisRes = await getRedisDataWithRetry(conversation_id);
        if (!redisRes) throw new Error("Conversation data not found!");
        // Handle agent creation
        const agent = await elevenlabs.conversationalAi.agents.create({
          name: `Agent for ${conversation_id}`,
          conversationConfig: {
            tts: { voiceId: voice.voiceId },
            agent: {
              prompt: {
                prompt:
                  analysis.data_collection_results.agent_description?.value ??
                  "You are a helpful assistant.",
                knowledgeBase: redisRes.knowledgeBase,
              },
              firstMessage: "Hello, how can I help you today?",
            },
          },
        });
        console.log("Agent created", { agent: agent.agentId });
        // Send email to user
        console.log("Sending email to", redisRes.email);
        await resend.emails.send({
          from: process.env.RESEND_FROM_EMAIL!,
          to: redisRes.email,
          subject: "Your ElevenLabs agent is ready to chat!",
          react: EmailTemplate({ agentId: agent.agentId }),
        });
      } catch (error) {
        console.error(error);
        return NextResponse.json({ error }, { status: 500 });
      }
    }
  }

  return NextResponse.json({ received: true }, { status: 200 });
}

const constructWebhookEvent = async (req: NextRequest, secret?: string) => {
  const body = await req.text();
  const signatureHeader = req.headers.get("ElevenLabs-Signature");

  return await elevenlabs.webhooks.constructEvent(body, signatureHeader, secret);
};

async function getRedisDataWithRetry(
  conversationId: string,
  maxRetries = 5
): Promise<{
  email: string;
  knowledgeBase: Array<{
    id: string;
    type: "file" | "url";
    name: string;
  }>;
} | null> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const data = await redis.get(conversationId);
      return data as any;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      console.log(`Redis get attempt ${attempt} failed, retrying...`);
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }
  }
  return null;
}
```

Esaminiamo ogni passaggio nel dettaglio.

### Verifica il segreto del webhook e costruisci il payload del webhook

Quando riceviamo la richiesta del webhook, verifichiamo prima il segreto del webhook e costruiamo il payload del webhook.

**`./app/api/convai-webhook/route.ts`**

```ts ./app/api/convai-webhook/route.ts
// ...

export async function POST(req: NextRequest) {
  const secret = process.env.ELEVENLABS_CONVAI_WEBHOOK_SECRET;
  const { event, error } = await constructWebhookEvent(req, secret);
  // ...
}

// ...
const constructWebhookEvent = async (req: NextRequest, secret?: string) => {
  const body = await req.text();
  const signatureHeader = req.headers.get("ElevenLabs-Signature");

  return await elevenlabs.webhooks.constructEvent(body, signatureHeader, secret);
};

async function getRedisDataWithRetry(
  conversationId: string,
  maxRetries = 5
): Promise<{
  email: string;
  knowledgeBase: Array<{
    id: string;
    type: "file" | "url";
    name: string;
  }>;
} | null> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const data = await redis.get(conversationId);
      return data as any;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      console.log(`Redis get attempt ${attempt} failed, retrying...`);
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }
  }
  return null;
}
```

### Crea un design vocale personalizzato in base a `voice_description`

Usando `voice_description` dal payload del webhook, creiamo un design vocale personalizzato.

**`./app/api/convai-webhook/route.ts`**

```ts ./app/api/convai-webhook/route.ts {5}
// ...

// Design the voice
const voicePreview = await elevenlabs.textToVoice.createPreviews({
  voiceDescription: analysis.data_collection_results.voice_description.value,
  text: "The night air carried whispers of betrayal, thick as London fog. I adjusted my cufflinks - after all, even spies must maintain appearances, especially when the game is afoot.",
});
const voice = await elevenlabs.textToVoice.createVoiceFromPreview({
  voiceName: `voice-${conversation_id}`,
  voiceDescription: `Voice for ${conversation_id}`,
  generatedVoiceId: voicePreview.previews[0].generatedVoiceId,
});

// ...
```

### Recupera i documenti della knowledge base dallo stato della conversazione archiviato in Redis

Il caricamento dei documenti potrebbe richiedere più tempo dell'analisi dei dati del webhook, quindi dovremo interrogare periodicamente lo stato della conversazione in Redis finché i documenti non saranno stati caricati.

**`./app/api/convai-webhook/route.ts`**

```ts ./app/api/convai-webhook/route.ts
// ...

// Get the knowledge base from redis
const redisRes = await getRedisDataWithRetry(conversation_id);
if (!redisRes) throw new Error("Conversation data not found!");
// ...

async function getRedisDataWithRetry(
  conversationId: string,
  maxRetries = 5
): Promise<{
  email: string;
  knowledgeBase: Array<{
    id: string;
    type: "file" | "url";
    name: string;
  }>;
} | null> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const data = await redis.get(conversationId);
      return data as any;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      console.log(`Redis get attempt ${attempt} failed, retrying...`);
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }
  }
  return null;
}
```

### Crea un agente ElevenLabs per gli utenti in base a `agent_description` fornito

Crea l'agente ElevenLabs per l'utente in base a `agent_description` fornito e associa all'agente il design vocale e la knowledge base appena creati.

**`./app/api/convai-webhook/route.ts`**

```ts ./app/api/convai-webhook/route.ts {7,11}
// ...

// Handle agent creation
const agent = await elevenlabs.conversationalAi.agents.create({
  name: `Agent for ${conversationId}`,
  conversationConfig: {
    tts: { voiceId: voice.voiceId },
    agent: {
      prompt: {
        prompt:
          analysis.data_collection_results.agent_description?.value ??
          "You are a helpful assistant.",
        knowledgeBase: redisRes.knowledgeBase,
      },
      firstMessage: "Hello, how can I help you today?",
    },
  },
});
console.log("Agent created", { agent: agent.agentId });

// ...
```

### Invia un'email all'utente per informarlo che il suo agente ElevenLabs personalizzato è pronto a conversare

Dopo aver creato l'agente, puoi inviare un'email all'utente per informarlo che il suo agente ElevenLabs personalizzato è pronto a conversare.

**`./app/api/convai-webhook/route.ts`**

```ts ./app/api/convai-webhook/route.ts
import { Resend } from "resend";

import { EmailTemplate } from "@/components/email/post-call-webhook-email";

// ...

// Send email to user
console.log("Sending email to", redisRes.email);
await resend.emails.send({
  from: process.env.RESEND_FROM_EMAIL!,
  to: redisRes.email,
  subject: "Your ElevenLabs agent is ready to chat!",
  react: EmailTemplate({ agentId: agent.agentId }),
});

// ...
```

Puoi usare [new.email](https://new.email/), un pratico strumento del team di Resend, per progettare liberamente i template delle tue email. Quando il template ti soddisfa, crea un nuovo componente e aggiungi l'ID dell'agente come prop.

**`./components/email/post-call-webhook-email.tsx`**

```tsx ./components/email/post-call-webhook-email.tsx {14}
import {
  Body,
  Button,
  Container,
  Head,
  Html,
  Section,
  Text,
  Tailwind,
} from "@react-email/components";
import * as React from "react";

const EmailTemplate = (props: any) => {
  const { agentId } = props;
  return (
    <Html>
      <Head />
      <Tailwind>
        <Body className="bg-[#151516] font-sans">
          <Container className="mx-auto my-[40px] max-w-[600px] rounded-[8px] bg-[#0a1929] p-[20px]">
            {/* Top Section */}
            <Section className="mb-[32px] mt-[32px] text-center">
              <Text className="m-0 text-[28px] font-bold text-[#9c27b0]">
                Your ElevenLabs agent is ready to chat!
              </Text>
            </Section>

            {/* Content Area with Icon */}
            <Section className="mb-[32px] text-center">
              {/* Circle Icon with Checkmark */}
              <div className="mx-auto mb-[24px] flex h-[80px] w-[80px] items-center justify-center rounded-full bg-gradient-to-r from-[#9c27b0] to-[#3f51b5]">
                <div className="text-[40px] text-white">✓</div>
              </div>

              {/* Descriptive Text */}
              <Text className="mb-[24px] text-[18px] text-white">
                Your ElevenLabs agent is ready to chat!
              </Text>
            </Section>

            {/* Call to Action Button */}
            <Section className="mb-[32px] text-center">
              <Button
                href={`https://elevenlabs.io/app/talk-to?agent_id=${agentId}`}
                className="box-border rounded-[8px] bg-[#9c27b0] px-[40px] py-[20px] text-[24px] font-bold text-white no-underline"
              >
                Chat now!
              </Button>
            </Section>

            {/* Footer */}
            <Section className="mt-[40px] border-t border-[#2d3748] pt-[20px] text-center">
              <Text className="m-0 text-[14px] text-white">
                Powered by{" "}
                <a
                  href="https://elevenlabs.io/conversational-ai"
                  target="_blank"
                  rel="noopener noreferrer"
                  className="underline transition-colors hover:text-gray-400"
                >
                  ElevenLabs Agents
                </a>
              </Text>
            </Section>
          </Container>
        </Body>
      </Tailwind>
    </Html>
  );
};

export { EmailTemplate };
```

## Avvia l'app

Per eseguire l'app in locale end-to-end, devi prima avviare il server di sviluppo Next.js, quindi eseguire il tunnel ngrok in un terminale separato per esporre a Internet l'handler del webhook.

* Terminale 1:
  * Esegui `pnpm dev` per avviare il server di sviluppo Next.js.

```bash
pnpm dev
```

* Terminale 2:
  * Esegui `ngrok http 3000` per esporre a Internet l'handler del webhook.

```bash
ngrok http 3000
```

Ora apri [http://localhost:3000](http://localhost:3000) e inizia a progettare il tuo agente ElevenLabs personalizzato, con la tua voce!

## Passaggi successivi

#### [ElevenAgents](/docs/it/eleven-agents/quickstart)

Esplora la piattaforma completa ElevenAgents per creare e distribuire agenti vocali.

#### [Analisi dell'agente](/docs/it/eleven-agents/customization/agent-analysis)

Esamina i dati delle conversazioni, la valutazione del successo e le metriche sulle prestazioni dell'agente.