[ 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
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
/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
- 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>). - Looks up the Instantly thread for the lead via
shared/instantly_reply.mjs.findLeadThread()— needslast_email_id,eaccount,subjectso the bridge can later POST/emails/replywith the correct threading. - Calls Repliq via
shared/repliq.mjs.launchVideoScale()with:
templateId: from arg, elseprocess.env.<id>(currently711266a6= "Paradox template 2")url: companyUrl (the lead's site, used as background)firstName,lastName,email: passed through for personalizationwebhookUrl:https://bridge.your-domain.example/repliq-callback?token=<<id>>
- 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..." } } ``
- Posts ONE Slack thread message confirming the queue: "Generating Repliq video for
<firstName>(<leadEmail>) — auto-replying via Instantly when ready (~60s)." - Exits. The async delivery happens entirely inside the bridge's
/repliq-callbackroute — when Repliq POSTs back ~60s later withvideoSuccess: "YES"andplaceholder.videoLink, the bridge:
- Renders
shared/<id>/<replyTemplate>.mdwith{{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 — Repliq API client (
getCredits,listTemplates,launchVideoScale) - shared/instantly_reply.mjs — Instantly thread lookup + reply
- shared/<id>/ — reply email templates with
{{firstName}},{{videoLink}} - ia-outreach/data/repliq-pending.json — pending callback context store
- shared/claude_bridge.mjs — owns
/repliq-callbackroute (search forhandleRepliqCallback)
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
- 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
launchVideoScalereturns. 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 creditsif 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 (
P7webhook) — useful for engagement analytics later. - Multi-template selection logic per client — pass
templateIdexplicitly until we have more than one.