> 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.

# Zbieranie i analiza danych z Agents Platform w Next.js

> **Note**
>
> **Samouczek** · Zakłada, że masz już za sobą [szybki start z ElevenAgents ](/docs/pl/eleven-agents/quickstart) i skonfigurowany projekt Next.js.

## Wprowadzenie

W tym samouczku dowiesz się, jak stworzyć agenta głosowego, który zbiera informacje od użytkownika podczas rozmowy, a następnie analizuje je, wyodrębnia dane w ustrukturyzowany sposób i wysyła je do aplikacji przez webhook po rozmowie.

## Wymagania

* Konto ElevenLabs z [kluczem API](https://elevenlabs.io/app/settings/api-keys).
* Node.js v18 lub nowszy zainstalowany na twoim komputerze.

## Konfiguracja

### Utwórz nowy projekt Next.js

Zalecamy użycie naszego [szablonu Agents Platform na v0.dev](https://v0.dev/community/nextjs-5TN93pl3bRS) jako punktu wyjścia dla aplikacji. To gotowa do wdrożenia aplikacja Next.js z już zintegrowanym agentem ElevenLabs.

### Skonfiguruj Agents Platform

Wykonaj kroki instalacji i konfiguracji z naszego [przewodnika po Next.js](/docs/pl/eleven-agents/guides/quickstarts/next-js). Następnie wróć tutaj, aby dodać zaawansowane funkcje.

## Konfiguracja agenta

#### Zaloguj się do ElevenLabs

Przejdź na [elevenlabs.io](https://elevenlabs.io/app/sign-up) i zaloguj się na swoje konto.

#### Utwórz nowego agenta

Przejdź do [Agents Platform > Agents](https://elevenlabs.io/app/agents/agents) i
utwórz nowego agenta z pustego szablonu.

#### Ustaw pierwszą wiadomość

Ustaw pierwszą wiadomość i określ zmienną dynamiczną platformy.

```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?
```

#### Ustaw prompt systemowy

Ustaw prompt systemowy. Możesz też dodać tu zmienne dynamiczne.

```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!
```

#### Skonfiguruj narzędzia klienta

Skonfiguruj poniższe narzędzie klienta do przechodzenia między krokami:

* Nazwa: `set_ui_state`
  * Opis: Użyj tego narzędzia po stronie klienta, aby przechodzić między różnymi stanami interfejsu.
  * Czekaj na odpowiedź: `true`
  * Limit czasu odpowiedzi (sekundy): 1
  * Parametry:
    * Typ danych: string
    * Identyfikator: step
    * Wymagane: true
    * Typ wartości: LLM Prompt
    * Opis: Krok, do którego ma przejść interfejs. Używaj tylko kroków zdefiniowanych w prompcie systemowym!

#### Ustaw głos agenta

Przejdź do karty `Voice` i ustaw głos dla agenta. Listę polecanych głosów dla Agents Platform znajdziesz w [dokumentacji Conversational Voice Design](/docs/pl/eleven-agents/customization/voice/best-practices/conversational-voice-design#voices).

#### Ustaw kryteria oceny

Przejdź do karty `Analysis` i dodaj nowe kryterium oceny.

* Nazwa: `all_data_provided`
  * Prompt: Oceń, czy użytkownik podał opis agenta, którego chce stworzyć, oraz opis głosu, jaki powinien mieć agent.

#### Skonfiguruj zbieranie danych

Możesz użyć analizy po rozmowie, aby wyodrębnić dane z konwersacji. W karcie `Analysis`, w sekcji `Data Collection`, dodaj poniższe elementy:

* Identyfikator: `voice_description`
  * `data-type`: `String`
  * Opis: Na podstawie opisu głosu, jaki użytkownik chce nadać agentowi, wygeneruj zwięzły opis głosu, uwzględniając wiek, akcent, ton i charakter, jeśli są dostępne.
* Identyfikator: `agent_description`
  * `data-type`: `String`
  * Opis: Na podstawie opisu agenta, którego użytkownik chce zaprojektować, wygeneruj prompt, który można wykorzystać do wytrenowania modelu działającego jako ten agent.

#### Skonfiguruj webhook po rozmowie

[Webhooki po rozmowie](https://elevenlabs.io/docs/eleven-agents/workflows/post-call-webhooks) służą do powiadamiania cię, gdy rozmowa się kończy oraz gdy analiza i wyodrębnianie danych zostaną ukończone.

W tym przykładzie webhook po rozmowie wykonuje kilka kroków:

1. Tworzy własny Voice Design na podstawie `voice_description`.
2. Tworzy agenta ElevenLabs dla użytkowników na podstawie podanego przez nich `agent_description`.
3. Pobiera dokumenty bazy wiedzy ze stanu rozmowy zapisanego w Redis i dołącza bazę wiedzy do agenta.
4. Wysyła użytkownikowi e-mail z informacją, że jego własny agent ElevenLabs jest gotowy do rozmowy.

Podczas pracy lokalnie potrzebujesz narzędzia takiego jak [ngrok](https://ngrok.com/), aby udostępnić lokalny serwer w internecie.

```bash
ngrok http 3000
```

Przejdź do [ustawień Agents Platform](https://elevenlabs.io/app/agents/settings) i w sekcji `Post-Call Webhook` utwórz nowy webhook, a następnie wklej adres URL ngrok: `https://<your-url>.ngrok-free.app/api/convai-webhook`.

Po zapisaniu webhooka otrzymasz sekret webhooków. Przechowuj go bezpiecznie, ponieważ później musisz ustawić go w pliku `.env`.

## Zintegruj zaawansowane funkcje

### Skonfiguruj bazę danych Redis do przechowywania stanu rozmowy

W tym przykładzie używamy Redis do przechowywania stanu rozmowy. Dzięki temu po zakończeniu rozmowy możemy pobrać dokumenty bazy wiedzy ze stanu rozmowy.

Jeśli wdrażasz aplikację na Vercel, możesz skonfigurować integrację [Upstash for Redis](https://vercel.com/marketplace/upstash) albo założyć darmowe [konto Upstash](https://upstash.com/) i utworzyć nową bazę danych.

### Skonfiguruj Resend do wysyłania e-maili po rozmowie

W tym przykładzie używamy Resend do wysyłania użytkownikowi e-maila po rozmowie. Aby to zrobić, musisz utworzyć darmowe [konto Resend](https://resend.com/) i skonfigurować nowy klucz API.

### Ustaw zmienne środowiskowe

W katalogu głównym projektu utwórz plik `.env` i dodaj poniższe zmienne:

```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=
```

### Skonfiguruj zabezpieczenia i uwierzytelnianie

Aby zabezpieczyć agenta ElevenLabs, włącz uwierzytelnianie na karcie `Security` w konfiguracji agenta.

Po włączeniu uwierzytelniania musisz utworzyć podpisany URL w bezpiecznym środowisku po stronie serwera, aby rozpocząć rozmowę z agentem. W Next.js możesz to zrobić, konfigurując nową trasę 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 });
  }
}
```

### Rozpocznij sesję rozmowy

Aby rozpocząć rozmowę, najpierw wywołaj trasę API, aby uzyskać podpisany URL, a następnie użyj hooka `useConversation`, aby skonfigurować sesję rozmowy.

**`./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]);
  // ...
}
```

### Narzędzie klienta i zmienne dynamiczne

W konfiguracji agenta wcześniej zarejestrowałeś narzędzie klienta `set_ui_state`, aby umożliwić agentowi przechodzenie między różnymi stanami interfejsu. Aby wszystko połączyć, musisz przekazać implementację narzędzia klienta w opcjach `conversation.startSession`.

W tym miejscu możesz też przekazać do rozmowy zmienne dynamiczne.

**`./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}`;
    },
  },
});
```

### Przesyłanie dokumentów do bazy wiedzy

Na etapie `Training` agent poprosi użytkownika o przesłanie dokumentów lub podanie adresów URL publicznych stron z informacjami, które powinny być dostępne dla jego agenta. Możesz tu użyć nowej funkcji `after` z [Next.js 15](https://nextjs.org/docs/app/api-reference/functions/after), aby umożliwić przesyłanie dokumentów w tle.

Utwórz nową akcję serwera `upload`, która obsłuży tworzenie bazy wiedzy po wysłaniu formularza. Gdy wszystkie dokumenty bazy wiedzy zostaną utworzone, zapisz identyfikator rozmowy i identyfikatory bazy wiedzy w bazie 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");
}
```

## Obsługa webhooka po rozmowie

[Webhook po rozmowie](/docs/pl/eleven-agents/workflows/post-call-webhooks) uruchamia się po zakończeniu rozmowy oraz zakończeniu analizy i ekstrakcji danych.

Dzieje się tu kilka rzeczy:

1. Weryfikacja sekretu webhooka i utworzenie ładunku webhooka.
2. Utworzenie własnego projektu głosu na podstawie `voice_description`.
3. Utworzenie agenta ElevenLabs dla użytkownika na podstawie podanego przez niego `agent_description`.
4. Pobranie dokumentów bazy wiedzy ze stanu rozmowy zapisanego w Redis i dołączenie bazy wiedzy do agenta.
5. Wysłanie użytkownikowi e-maila z informacją, że jego własny agent ElevenLabs jest gotowy do rozmowy.

**`./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;
}
```

Omówmy każdy krok szczegółowo.

### Zweryfikuj sekret webhooka i utwórz ładunek webhooka

Po otrzymaniu żądania webhooka najpierw weryfikujemy jego sekret i tworzymy ładunek webhooka.

**`./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;
}
```

### Utwórz własny projekt głosu na podstawie `voice_description`

Na podstawie `voice_description` z ładunku webhooka tworzymy własny projekt głosu.

**`./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,
});

// ...
```

### Pobierz dokumenty bazy wiedzy ze stanu rozmowy zapisanego w Redis

Przesyłanie dokumentów może trwać dłużej niż analiza danych webhooka, więc musimy odpytywać stan rozmowy w Redis, aż dokumenty zostaną przesłane.

**`./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;
}
```

### Utwórz agenta ElevenLabs dla użytkownika na podstawie podanego `agent_description`

Utwórz agenta ElevenLabs dla użytkownika na podstawie podanego przez niego `agent_description` i dołącz do niego nowy projekt głosu oraz bazę wiedzy.

**`./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 });

// ...
```

### Wyślij użytkownikowi e-mail z informacją, że jego własny agent ElevenLabs jest gotowy do rozmowy

Gdy agent zostanie utworzony, możesz wysłać użytkownikowi e-mail z informacją, że jego własny agent ElevenLabs jest gotowy do rozmowy.

**`./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 }),
});

// ...
```

Możesz użyć [new.email](https://new.email/), przydatnego narzędzia zespołu Resend, do swobodnego projektowania szablonów e-maili. Gdy szablon będzie gotowy, utwórz nowy komponent i dodaj identyfikator agenta jako 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 };
```

## Uruchom aplikację

Aby uruchomić aplikację lokalnie end-to-end, najpierw uruchom serwer deweloperski Next.js, a potem w osobnym terminalu tunel ngrok, aby udostępnić handler webhooka w internecie.

* Terminal 1:
  * Uruchom `pnpm dev`, aby włączyć serwer deweloperski Next.js.

```bash
pnpm dev
```

* Terminal 2:
  * Uruchom `ngrok http 3000`, aby udostępnić handler webhooka w internecie.

```bash
ngrok http 3000
```

Teraz otwórz [http://localhost:3000](http://localhost:3000) i zacznij projektować własnego agenta ElevenLabs swoim głosem!

## Kolejne kroki

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

Poznaj całą platformę ElevenAgents do tworzenia i wdrażania agentów głosowych.

#### [Analiza agenta](/docs/pl/eleven-agents/customization/agent-analysis)

Sprawdzaj dane rozmów, ocenę sukcesu i wskaźniki działania agenta.