---
title: Make a personalized video for every prospect from one recording
description: Record one sales video, then get one copy per prospect with their name, their company and their website on screen, each as its own MP4.
---
Record one sales outreach video, or prospecting video, and get a personalized copy for every prospect on your list. Each copy shows the prospect's name and company as text, and a screenshot of their website beside you. Each one is its own MP4, named for the prospect, ready to attach or link in your email.

Use invideo for this when you want one template timeline and a copy per row, so a fix to the template is one edit and every copy stays editable.

Your agent never sends the emails or messages. You send them from your own email or sales tool.

## Quickest path

Set up your agent first with [invideo for AI agents](/agents). Then give it this prompt.

```text
Use pitch.mp4 as the template. For each row in prospects.csv, make a copy
with "Hi {first_name}" and the company name on screen, and a screenshot
of their website on the right for the first ten seconds. Export one MP4
per prospect named by company. Do five, show me, then do the rest.
```

In a chat app, you drag the recording and paste the list into the chat. In a terminal, the agent reads the CSV and the folder.

## What you need

These are the inputs the agent asks for.

- One recording of you, talking to the camera. Leave space on one side for the screenshot, and keep your opening line general, like "Hi there".
- The prospect list as a CSV: first name, company, website.
- The text to show, like "Hi Priya, this one is for Acme".
- How long the screenshot stays on screen.
- The shape. 16:9 is usual for email. Use 1:1 or 9:16 for LinkedIn messages.

## Steps

The agent builds the template once on a branch, then copies it per prospect.

<Steps>
<Step title="Bring the recording in">
The agent makes a project with `new_project`, uploads the recording with `start_upload` and `add_media`, and keeps the prospect list in a brief made with `new_brief`. Expected result: a sequence URL for the recording and a brief URL.
</Step>
<Step title="Build the template">
The agent makes a timeline with `new_timeline` and creates a branch with `timeline_history`. It places the recording with `place_media` and `linked_audio` true. It adds a text clip with `add_text`, with a placeholder like "Hi NAME, this one is for COMPANY", and an empty spot for the screenshot. Expected result: a template timeline and a new `state_token`. The agent shows you a `capture` of it before going on.
</Step>
<Step title="Take each website screenshot">
invideo does not capture web pages. The agent opens each prospect's site with its own browser or screenshot tool, saves the image, then uploads it with `start_upload` and `add_media` on the timeline URL. Expected result: one image in the media bin per prospect, with an id.
</Step>
<Step title="Copy the template per prospect">
The agent runs `duplicate_timeline` with the prospect's company as `name`. It reads the copy with `get_timeline` and `timeline_id` for the new clip ids. Then it changes the text with `set_text` and places the screenshot with `place_media` and `set_position`. Expected result: one timeline per prospect, each with its own name, company and site.
</Step>
<Step title="Say each prospect's name, if you want it spoken">
This step is optional and costs credits. The agent uploads a short, clean clip of your voice with `start_upload` and `add_media`. It runs `find_models` with the job in words, picks one, and reads its inputs with `get_model`. A good pick for the voice is `seed_audio_1_0.ta2a`, and for the lips `sync_lipsync_v3.video`. It calls `generate` with `dry_run: true`, that clip's sequence URL as `reference_audio_1`, and a `prompt` that tags you as `@Audio1` with the line, like "Hi Priya". It tells you the price, then calls `generate` with `max_credits` set to that price and an `idempotency_key` per prospect. It runs the lip sync the same way, dry run first, with the recording as `video` and the new line as `audio`. It reads each result with `read` and places it over your opening line with `place_media`. Expected result: a sequence URL per prospect where your lips match the name. Use only your own voice. Some models decline to copy a voice without consent.
</Step>
<Step title="Check a sample">
The agent runs `capture` at the text and the screenshot on the first few copies, and `check_timeline`. It reads each name against the CSV. Expected result: the right name, company and site on every checked copy.
</Step>
<Step title="Export one file per prospect">
The agent runs `export` with `format` video, the copy's `timeline_id`, a `file_name` like `acme-priya`, and one `idempotency_key` per prospect. It runs the list in parts and marks each done row in the brief. Expected result: one export card per prospect with status `completed`.
</Step>
</Steps>

The terminal version of the same job:

```bash
invideo project new --title "Outreach Q4"
invideo agent register <project-url> --name <name>
export INVIDEO_AS=<name>
invideo asset upload <project-url> ./pitch.mp4
invideo context new-brief <project-url> --name "Q4 prospects" \
  --description "One video per row of prospects.csv"
invideo edit new <project-url> --name "Outreach template"
invideo asset upload <edit-url> ./shots/acme.png
invideo edit show <edit-url> --json
invideo edit change <edit-url> --timeline T1 --token <state_token> \
  --operations @copy-acme.json
invideo edit show <edit-url> --timeline T2 --mode window
invideo edit change <edit-url> --timeline T2 --token <state_token> \
  --operations @text-acme.json
invideo gen models <project-url> --query "seed audio"
invideo gen model <project-url> --model seed_audio_1_0.ta2a
invideo gen run <page-url> --model seed_audio_1_0.ta2a --dry-run \
  -i reference_audio_1=<voice-clip-url> -i prompt='Warm. @Audio1: "Hi Priya"'
invideo gen run <page-url> --model seed_audio_1_0.ta2a \
  --max-credits <price> --idempotency-key acme-priya-name \
  -i reference_audio_1=<voice-clip-url> -i prompt='Warm. @Audio1: "Hi Priya"'
invideo gen models <project-url> --query "lip sync"
invideo gen model <project-url> --model sync_lipsync_v3.video
invideo gen run <page-url> --model sync_lipsync_v3.video --dry-run \
  -i video=<recording-url> -i audio=<name-line-url>
invideo gen run <page-url> --model sync_lipsync_v3.video \
  --max-credits <price> --idempotency-key acme-priya-sync \
  -i video=<recording-url> -i audio=<name-line-url>
invideo edit capture <edit-url> --timeline T2 --at 1,5 --out ./frames
invideo edit export <edit-url> --format video --timeline T2 \
  --file-name acme-priya --idempotency-key acme-priya-v1 --json
invideo status <card-url> --wait
invideo notebook show <card-url> --out ./videos
mv ./videos/export.mp4 ./videos/acme-priya.mp4
```

Where the values come from: `<project-url>` from `invideo project new`, and `<edit-url>` from `invideo edit new`. `<name>` is the name you choose in `invideo agent register`, and `<page-url>` is a notebook page from `invideo notebook show <project-url>`. `<state_token>` comes from the `invideo edit show` right before each change, because the first change makes the old token stale. `<recording-url>` and `<voice-clip-url>` are the `url` values that `invideo asset upload` returns. `<name-line-url>` is the sequence URL from the voice run, and `<price>` is each dry run's `estimated_credits`. `<card-url>` is `.sequences[0]` of what the export returns. `invideo status` does not download, and `invideo notebook show` saves the file as `export.mp4`, so the agent renames it.

The key edits copy the template, then set the text and place the screenshot on the copy. These are two calls, because the copy's clip ids come from a read in between. The ops files are bare lists. At 30 fps, frame 300 is 10 seconds.

The file `copy-acme.json`, run on `T1`:

```json
[
  { "op": "duplicate_timeline", "timeline": "T1", "name": "Acme" }
]
```

The file `text-acme.json`, run on the copy `T2`. The ids `c12` and `c13` come from `invideo edit show` on `T2`, and `m4` is the screenshot's media bin id from `invideo asset upload`.

```json
[
  { "op": "set_text", "clip": "c12",
    "text": "Hi Priya, this one is for Acme" },
  { "op": "place_media", "asset": "m4", "track": "V2", "at_f": 0,
    "source_start_seconds": 0, "source_end_seconds": 10,
    "mode": "place_on_top", "linked_audio": false },
  { "op": "set_position", "clip": "c13", "x": 480, "y": 0 }
]
```

## Cost and time

Edits and exports are free. Generation and transcripts cost credits. Paid stock costs a small credit charge, or one of your workspace's iStock licenses. Your agent runs a dry run first and tells you the price before it spends anything.

- Free: the upload, every copy and text change, `capture`, every `export`.
- Costs credits: nothing on this page, unless you add captions with `audio.transcript` or the spoken name with `seed_audio_1_0.ta2a` and `sync_lipsync_v3.video`.

Each copy takes a minute or two of agent time and an export. A long list takes hours, so run it in parts. See [Credits](/account/credits) for prices.

## If something goes wrong

Most problems are wrong ids on a copy, or a list that stops halfway.

| Symptom | Fix |
| --- | --- |
| `get_model` or `generate` refuses the model as not found | Models retire. Run `find_models` with the job in words, use the best match, and tell the person which model you used. |
| Exit code 6, or `status` is `held` | A safety review, not a refusal. Wait, then run `invideo status <url> --wait` again. Do not resend. If it ends `failed`, change the input the review flagged. |
| `set_text` changed the template, not the copy | The clip id was the template's. Read the copy with `get_timeline` and its `timeline_id`, then use its ids. |
| A name runs off the frame | Lower the font size with `set_text_style`, or put the company on a second line. |
| The screenshot shows a cookie banner | Close the banner in the browser and take the screenshot again. |
| A site blocks the browser, so there is no screenshot | Skip the screenshot for that row and tell the person. |
| A session ends halfway through the list | Read the brief for the done rows and start from the next one. |
| `timeout` on `export` | Do not export again. `read` the thread or call `status`. |
| CLI exit 3 | The edit changed since your read. Run `invideo edit show` and rebuild. |
| The download folder is empty | `invideo status` does not download. Run `invideo notebook show <card-url> --out ./videos`. |
| The file is `export.mp4`, not `acme-priya` | `file_name` does not name the saved file. Rename it after the download. |
| CLI exit 7 | Nothing ran. Run the same command again. |
| Avoid: exporting without `timeline_id` | Instead: pass the copy's id. Without it, you export the main timeline. |
| Avoid: sending the emails for the person | Instead: hand over the files and the list. The person sends. |

## Prompts to try

Each prompt is a real variation of this job.

```text
Same template, but 9:16 for LinkedIn messages. Put the name at the top
and the screenshot in the lower half.
```

```text
Add each prospect's logo in the corner instead of a site screenshot.
The logos are in ./logos, named by company.
```

```text
Add burned-in captions to the template from my voice, then make the
copies for the 40 new rows in prospects.csv.
```

## When to use something else

invideo does not send emails, track views or host a landing page per prospect. Use your email or sales engagement tool for that. To say each prospect's name out loud in your own voice, the agent can clone your voice with `seed_audio_1_0.ta2a` and re-sync your lips with `sync_lipsync_v3.video`, but test it on a few names first: a wrong name in your own voice hurts more than text on screen. For a live screen recording per prospect, use a screen recorder and bring each file in with `start_upload`.

## Related

- [Make a personalized video message from Santa or an AI presenter](/how-to/personal/personalized-message-video)
- [Turn a CSV into many videos with a script](/how-to/developers/csv-to-videos)
- [Make a launch video from your product URL and screenshots](/how-to/marketing/launch-video)
- [Add word-by-word captions](/how-to/creators/word-by-word-captions)
- [Check your video before you say it is done](/how-to/check-your-video)
- [Know the cost before you spend](/how-to/cost-before-you-spend)