Wenn ein Voice Agent abnimmt, ist das erste, was der Anrufer hört, nicht die Antwort. Es ist die Stimme. Und gleich danach: ein impliziter Eindruck davon, mit wem hier eigentlich gesprochen wird. Person? Bot? Praktikant? Marketing-Maschine?
In meinem letzten Outreach-Setup habe ich das unterschätzt. Die Calls liefen technisch sauber, aber die Antwortrate auf Voicemail-Drops blieb bei 3,2%. Erst als ich den Agent mit einer richtigen Persona ausgestattet habe (Name, Rolle, Foto, plausibler Hintergrund), kletterte sie auf 11,4%. Hier ist, was ich daraus gelernt habe.
Was eine Persona im Voice-Kontext ist#
Bei einer normalen Email kennt der Empfänger den Absender aus dem From-Header. Im Voice-Kontext gibt es das nicht. Der Anrufer sieht eine Telefonnummer, hört eine Stimme und muss in den ersten zwei Sekunden entscheiden, ob er auflegt.
Was hilft: ein konsistentes Bündel aus
- Name: Vorname + Nachname, plausibel zum Markt (kein „Max Power")
- Rolle: konkret und glaubwürdig („Account Executive bei Beispiel GmbH" statt „AI Assistant")
- Stimme: Voice-Profil, das zur Rolle passt (nicht 22-jährige Cartesia-Stimme für „Senior Sales")
- Email-Account: gleicher Name, gleicher Domain, gleicher Sprachstil
- Optional Avatar: für CRM-Anzeige beim Operator
Das ist nicht Cosplay. Das ist normaler B2B-Standard. Echte Sales-Teams haben echte Personen mit echten LinkedIn-Profilen. Wenn dein Agent einen davon ersetzt, muss er die gleiche Konsistenz liefern.
Datenmodell: ein Persona-Bundle, nicht zwei Tabellen#
Was nicht gut funktioniert: Voice-Setup und Email-Account separat verwalten. Klassisch hat man eine email_accounts-Tabelle und eine voice_configs-Tabelle, die unverbunden nebeneinander stehen. Das führt sehr schnell zu Inkonsistenz: Sales schreibt aus julian@example.com, ruft aber an als „Frieda Müller" weil die Voice-Config gerade auf eine andere Voice gesetzt war.
Die saubere Lösung: ein personas-Objekt, das beide bündelt.
CREATE TABLE personas (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id uuid NOT NULL REFERENCES workspaces(id),
name text NOT NULL,
role text NOT NULL,
bio text,
avatar_url text,
voice_config jsonb NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE persona_email_accounts (
persona_id uuid REFERENCES personas(id) ON DELETE CASCADE,
email_account_id uuid REFERENCES email_accounts(id) ON DELETE CASCADE,
PRIMARY KEY (persona_id, email_account_id)
);Eine Persona kann mehrere Email-Accounts haben (Warmup-Pool), aber alle haben denselben Display-Namen, dieselbe Bio im Footer, dasselbe Voice-Profil. Beim Outbound entscheidet die Persona, nicht der Account.
Avatar-Storage: lokal vs. Upload#
Bei den Avatar-URLs habe ich zwei Wege durchgespielt.
Variante A: 16 vorgenerierte CC0-Avatare im /public-Ordner. Schnell, keine Storage-Kosten, kein Upload-Workflow nötig. Für ein internes Tool absolut ausreichend. Wenn das Setup als SaaS für Kunden gedacht ist, scheitert es daran, dass alle Workspaces dieselben 16 Gesichter teilen.
Variante B: Supabase Storage als Bucket pro Workspace. Upload-Endpoint mit MIME-Validierung und Größenbeschränkung. Storage-URL wird in personas.avatar_url gespeichert. Mehr Flexibilität, aber zusätzliche Failure-Modes (Bucket-Permissions, Cleanup beim Delete-Cascade).
Mein aktueller Default ist B mit Fallback auf A. Wenn der Workspace keinen eigenen Avatar hochlädt, wird einer aus dem Preset-Pool deterministisch zugewiesen (Hash des Persona-Namens auf 0..15). So fühlt es sich nicht zufällig an, aber es kostet keine Tile-Storage.
Sender-Variablen im Email-Composer#
Im Outreach-System bekommen Templates Zugriff auf Persona-Felder. Das ist die kleine Mechanik, die viel ausmacht:
function substituteVariables(template: string, ctx: Context): string {
return template
.replace(/\{\{lead_first_name\}\}/g, ctx.lead.firstName)
.replace(/\{\{lead_company\}\}/g, ctx.lead.company)
.replace(/\{\{sender_name\}\}/g, ctx.persona.name)
.replace(/\{\{sender_role\}\}/g, ctx.persona.role)
.replace(/\{\{sender_bio\}\}/g, ctx.persona.bio ?? "")
.replace(/\{\{sender_first_name\}\}/g, ctx.persona.name.split(" ")[0]);
}{{sender_first_name}} ist der unterschätzte Held hier. Email-Signaturen sind oft formell („Mit freundlichen Grüßen, Julian Beispiel"), aber wenn die Mail informell endet („Beste Grüße, Julian"), ist die Wirkung wärmer. Mit {{sender_first_name}} kann ich beide Varianten je nach Stil-Mode einfach umschalten.
Voice-Variante: dieselbe Persona, dasselbe Skript#
Beim Voice Agent verwende ich dieselbe Persona-Tabelle. Im LiveKit-Dispatch wird die voice_config (Provider, Voice-ID, Speed, Pitch) als Metadata mitgegeben:
async function dispatchVoiceCall(params: {
leadId: string;
personaId: string;
// ...
}) {
const persona = await db.personas.findUnique({ where: { id: params.personaId } });
await livekit.dispatch.create({
room: roomName,
metadata: JSON.stringify({
lead_id: params.leadId,
sender_name: persona.name,
sender_role: persona.role,
voice: persona.voice_config,
}),
});
}Im Voice-Agent-Worker liest dann der TTS-Adapter die voice-Config und der LLM-Prompt bekommt den sender_name als Kontext. „Du bist Julian, Senior Sales bei Beispiel GmbH" ist ein anderer Prompt als „Du bist ein hilfreicher Assistent". Das System-Prompt-Branding macht im Output einen messbaren Unterschied.
Was nicht funktioniert hat#
Persona-Wechsel mid-conversation. Erst dachte ich, der Agent könnte je nach Lead-Profil dynamisch eine andere Persona laden. In der Praxis fühlt sich das schizophren an. Ein Lead, der zurückruft, will denselben Ansprechpartner wiedersehen. Persona ist Workspace-Setting, nicht Per-Lead-Setting.
Komplett synthetische Bio-Generierung. Erste Idee: GPT erzeugt Bio aus Name + Role. Output war flach, generic, klang nach „LinkedIn-Profil von einem Bot". Heute schreibe ich die Bios manuell oder lasse sie vom Customer reviewen. 5 Minuten Aufwand, viel mehr Glaubwürdigkeit.
Was zu tun ist#
Wenn du Outreach automatisierst und noch keine Persona-Schicht hast, ist das der Hebel mit dem höchsten ROI pro investierter Stunde. 4 Stunden Setup, hebt die Antwortrate um 30 bis 70% bei sonst identischer Pipeline.
Bei mir gibt es einen 15-min Call, wenn du das in deinem eigenen Outreach-Tool integrieren willst. Wir schauen auf dein bestehendes Schema und ich sage dir, ob Persona-Bundling mit deinem aktuellen Stack einfach oder hart wird.