# /repliq-video

> Generate a personalized Repliq video (Faris speaking, lead's website rendered behind him via the "Video Scale" template) and queue an auto-reply that fires through Instantly the moment Repliq's webhook says the video is 


# Repliq Personalized Video on Positive Reply

## Triggers

- `/repliq-video you@example.com John Smith https://creationmarket.com`
- "send Repliq video to you@example.com"
- "pošalji Repliq video za John Smith — you@example.com, x.com"
- (Future) Instantly webhook firing on lead status → Interested

## What this skill does

1. **Parses the brief** — pulls leadEmail (required), firstName, lastName, companyUrl, optional templateId / replyTemplate / campaignId. If companyUrl is missing, derives it from the email domain (`https://<domain>`).
2. **Looks up the Instantly thread** for the lead via `shared/instantly_reply.mjs.findLeadThread()` — needs `last_email_id`, `eaccount`, `subject` so the bridge can later POST `/emails/reply` with the correct threading.
3. **Calls Repliq** via `shared/repliq.mjs.launchVideoScale()` with:
   - `templateId`: from arg, else `process.env.<id>` (currently `711266a6` = "Paradox template 2")
   - `url`: companyUrl (the lead's site, used as background)
   - `firstName`, `lastName`, `email`: passed through for personalization
   - `webhookUrl`: `https://bridge.your-domain.example/repliq-callback?token=<<id>>`
4. **Stages the pending entry** to `ia-outreach/data/repliq-pending.json`:
   ```json
   {
     "<repliqId>": {
       "createdAt": "...",
       "leadEmail": "...",
       "leadFirstName": "...",
       "thread": { "last_email_id": "...", "eaccount": "...", "subject": "..." },
       "replyTemplate": "creation-market-v1",
       "channel": "C0...",
       "threadTs": "1714..."
     }
   }
   ```
5. **Posts ONE Slack thread message** confirming the queue: "Generating Repliq video for `<firstName>` (`<leadEmail>`) — auto-replying via Instantly when ready (~60s)."
6. **Exits.** The async delivery happens entirely inside the bridge's `/repliq-callback` route — when Repliq POSTs back ~60s later with `videoSuccess: "YES"` and `placeholder.videoLink`, the bridge:
   - Renders `shared/<id>/<replyTemplate>.md` with `{{firstName}}` + `{{videoLink}}`
   - Sends the reply via Instantly `/emails/reply`
   - Posts a follow-up in the same Slack thread with the video URL
   - Removes the pending entry

## Why webhook-driven (not polling)

Repliq has no documented status-polling endpoint. The `/launchTemplate` response is immediate but contains `placeholder.status: "pending"`. The only signal the video is ready is the webhook callback, which is why the skill registers our bridge URL and exits — the bridge owns the rest of the flow.

## Critical files

- [shared/repliq.mjs](../../../shared/repliq.mjs) — Repliq API client (`getCredits`, `listTemplates`, `launchVideoScale`)
- [shared/instantly_reply.mjs](../../../shared/instantly_reply.mjs) — Instantly thread lookup + reply
- [shared/<id>/](../../../shared/<id>/) — reply email templates with `{{firstName}}`, `{{videoLink}}`
- [ia-outreach/data/repliq-pending.json](../../../ia-outreach/data/repliq-pending.json) — pending callback context store
- [shared/claude_bridge.mjs](../../../shared/claude_bridge.mjs) — owns `/repliq-callback` route (search for `handleRepliqCallback`)

## Implementation outline (for the executing model)

```js
import 'dotenv/config';
import { writeFile, readFile } from 'fs/promises';
import { resolve } from 'path';
import { launchVideoScale } from '<repo>/shared/repliq.mjs';
import { findLeadThread } from '<repo>/shared/instantly_reply.mjs';

const PENDING = resolve('<repo>/ia-outreach/data/repliq-pending.json');
const CALLBACK = `https://bridge.your-domain.example/repliq-callback?token=${process.env.<id>}`;

// 1. Parse: leadEmail, firstName, lastName, companyUrl (or derive), templateId, replyTemplate
// 2. Look up Instantly thread
const thread = await findLeadThread(leadEmail, { campaignId });
if (!thread) throw new Error(`No Instantly thread for ${leadEmail}`);

// 3. Launch
const { id, placeholder } = await launchVideoScale({
  templateId: templateId || process.env.<id>,
  url: companyUrl,
  firstName, lastName, email: leadEmail,
  webhookUrl: CALLBACK,
});

// 4. Persist pending
const store = JSON.parse((await readFile(PENDING, 'utf-8').catch(() => '{}')) || '{}');
store[id] = {
  createdAt: new Date().toISOString(),
  leadEmail, leadFirstName: firstName,
  thread: {
    last_email_id: thread.last_email_id,
    eaccount: thread.eaccount,
    subject: thread.subject,
  },
  replyTemplate: replyTemplate || 'creation-market-v1',
  channel,         // from bridge context
  threadTs,        // from bridge context
};
await writeFile(PENDING, JSON.stringify(store, null, 2) + '\n');

// 5. Single Slack message — done.
```

## Hard rules

- **One Slack message only.** Skill posts the "Generating..." line. The follow-up confirmation is posted by the bridge callback, not by the skill. Do NOT post both.
- **Never wait for the video.** The skill exits after `launchVideoScale` returns. Polling here defeats the architecture and will time out.
- **Do not parse the videoLink yourself or worry about the custom domain.** Repliq returns the videoLink already on the configured domain (your-domain.example subdomain CNAME'd to Repliq, set up account-side). Trust the response.
- **Credit awareness.** Each generation burns 1 credit. Run `node shared/repliq.mjs credits` if you suspect the account is dry — 401/403 errors get translated by the lib but quota errors look like 400s with quota messages.
- **Retry behavior.** If the webhook never fires within ~5 minutes, the pending entry is stale. Re-running the skill creates a new id and a new pending entry — old ones are harmless but should be cleaned up periodically.

## Out of scope for this skill

- Phase 2 Instantly auto-trigger on positive reply — that's a separate webhook receiver in the bridge, reusing this same skill code path.
- Repliq view-completion tracking (`P7` webhook) — useful for engagement analytics later.
- Multi-template selection logic per client — pass `templateId` explicitly until we have more than one.