Billionmail/BillionMail · error

no voice configured: set VOICE_SAMPLE_URL or VOICE_DEFAULT_I

Error message

no voice configured: set VOICE_SAMPLE_URL or VOICE_DEFAULT_ID

What it means

resolveVoiceID resolves which voice to use for video generation. When neither a per-prospect sample URL nor the VOICE_DEFAULT_ID environment variable provides a voice ID, it returns this error instead of proceeding, because lip-sync and TTS stages cannot run without a voice.

Source

Thrown at core/internal/service/video_gen/orchestrator.go:396

func resolveVoiceID(ctx context.Context, cfg VoiceConfig) (string, error) {
	sampleURL := os.Getenv("VOICE_SAMPLE_URL")
	defaultID := os.Getenv("VOICE_DEFAULT_ID")

	if sampleURL != "" {
		resp, err := withRetry(func() (*VoiceCloneResponse, error) {
			return CloneVoice(ctx, cfg, "sender-voice", sampleURL)
		})
		if err != nil {
			return "", fmt.Errorf("voice clone: %w", err)
		}
		return resp.ID, nil
	}

	if defaultID != "" {
		return defaultID, nil
	}

	return "", fmt.Errorf("no voice configured: set VOICE_SAMPLE_URL or VOICE_DEFAULT_ID")
}

// pollLipSync polls lip sync status until completed or timeout.
func pollLipSync(ctx context.Context, cfg LipSyncConfig, jobID string) (string, error) {
	deadline := time.Now().Add(lipSyncTimeout)
	for {
		if time.Now().After(deadline) {
			return "", fmt.Errorf("lip sync timed out after %v", lipSyncTimeout)
		}

		resp, err := CheckLipSyncStatus(ctx, cfg, jobID)
		if err != nil {
			return "", fmt.Errorf("check lip sync status: %w", err)
		}

		switch resp.Status {
		case "completed":
			if resp.VideoURL == "" {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Set the VOICE_DEFAULT_ID environment variable to a valid voice ID from the TTS/lip-sync provider
  2. Or provide a VoiceSampleURL on the pipeline input/prospect so a cloned voice can be created
  3. Verify the env file is actually loaded into the container/process (docker compose env, systemd EnvironmentFile)
  4. Add startup validation that fails fast when neither var is set

Example fix

// before
id, err := resolveVoiceID(ctx, input)
// after
if input.VoiceSampleURL == "" && os.Getenv("VOICE_DEFAULT_ID") == "" {
	log.Fatal("voice not configured: set VOICE_SAMPLE_URL or VOICE_DEFAULT_ID before starting")
}
id, err := resolveVoiceID(ctx, input)
Defensive patterns

Strategy: validation

Validate before calling

if input.VoiceSampleURL == "" && os.Getenv("VOICE_DEFAULT_ID") == "" {
	return fmt.Errorf("pipeline requires VOICE_SAMPLE_URL or VOICE_DEFAULT_ID")
}

Try / catch

id, err := resolveVoiceID(ctx, input)
if err != nil {
	return fmt.Errorf("voice resolution: %w", err)
}

Prevention

When it happens

Trigger: RunPipeline reaches the voice-resolution step while the pipeline input has an empty VoiceSampleURL and the VOICE_DEFAULT_ID environment variable is unset or empty, so the function falls through all candidates and returns the error.

Common situations: Deploying the video-gen service without the voice env vars configured; a prospect record missing a recorded voice sample; an .env file not loaded in Docker; typo'd env var name (VOICE_DEFAULT_ID vs VOICE_DEFAULTID).

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/17926b3797144fdb. Report an issue: GitHub.