PORTAL / LIBRARY / repliq-video

[ OUTBOUND ]

/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

Download the skill file (.md)

Placeholders like ACME Agency, <id> and you@example.com mark values that are per-agency — your install fills them with YOUR clients and accounts. If a section references a helper script you don't have yet, it ships with that workflow's install.

Repliq Personalized Video on Positive Reply

Triggers

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:
  1. 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..." } } ``

  1. Posts ONE Slack thread message confirming the queue: "Generating Repliq video for <firstName> (<leadEmail>) — auto-replying via Instantly when ready (~60s)."
  2. 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:

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

Implementation outline (for the executing model)

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

Out of scope for this skill