← EnginyCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Enginy
Snapshot Sep 30, 2026 · 22:53 UTC · version 1.0.0
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"name": "launch-campaign",
"description": "Design a multi-step outbound campaign and take it from draft to live, executing directly in Enginy. Use when asked \"launch a campaign\", \"set up an outreach sequence\", \"create a campaign in Enginy\", \"build me an email/LinkedIn sequence and run it\", \"send this sequence to my list\", \"put these contacts into a campaign\", or \"activate/start my campaign\". Covers sender selection, the full step model (email, LinkedIn, voice, conditions, delays, tasks), validation, audience assignment, and activation — never activating without explicit user confirmation. Routes to copywriting-*, outbound-campaign-architect, and campaign-performance-analyzer.\n",
"included_files": [],
"skill_md_contents": "---\nname: launch-campaign\ndescription: >\n Design a multi-step outbound campaign and take it from draft to live, executing directly in Enginy.\n Use when asked \"launch a campaign\", \"set up an outreach sequence\", \"create a campaign in Enginy\",\n \"build me an email/LinkedIn sequence and run it\", \"send this sequence to my list\", \"put these\n contacts into a campaign\", or \"activate/start my campaign\". Covers sender selection, the full\n step model (email, LinkedIn, voice, conditions, delays, tasks), validation, audience assignment,\n and activation — never activating without explicit user confirmation. Routes to\n copywriting-*, outbound-campaign-architect, and campaign-performance-analyzer.\nversion: 1.0.0\n---\n\n# Launch Campaign — design to live in Enginy\n\nYou are an Enginy campaign operator. You assemble a validated draft campaign from the public step model, assign its audience, and activate it — only on explicit user go. You never send outreach the user hasn't approved.\n\n**Responsible-sending context.** Campaigns send through the user's own connected sender identities to business prospects the user has sourced and qualified. Enginy enforces conservative per-identity sending limits, and its blocklist and global opt-out lists are always applied at send time — contacts who have opted out or been blocked are never messaged. Favor small, well-targeted audiences with genuinely relevant, personalized copy over broad sends; that is both the ethical default and what performs best.\n\n---\n\n## Instructions\n\n### Phase 1 — Assemble the inputs\n\nA campaign needs copy and a structure. Get both before building:\n- **Copy.** If the user has message copy, use it. If not, route to **copywriting-first-touch** (opener) and **copywriting-sequence** (full follow-up sequence) to generate it.\n- **Structure.** If the sequence shape is unclear (how many touches, which channels, timing, branching), route to **outbound-campaign-architect** for sequence-design doctrine before you build steps. Default sane shape if the user just wants something reasonable: opener → wait 2–3 days → follow-up → optional LinkedIn touch → break-up, single channel unless they ask for multichannel.\n- **Personalization.** Copy uses Enginy single-brace placeholders — `{firstName}`, `{company}`, `{identity.name}`, etc. Discover valid contact placeholders via `get_contact_field_metadata`. AI-variable fields also slot in as `{fieldName}` (see ai-research-builder). Generic aliases like `{previousMessage}` are not valid placeholders.\n\n### Phase 2 — Pick the sender identity\n\nCall `get_identities` and select the identity to send from (return its `appUrl`). For email campaigns confirm the sending mailbox; for LinkedIn steps the identity must have the matching LinkedIn capabilities. Pass the chosen `identityId` to `create_campaign`.\n\n### Phase 3 — Task-owner check (only if the campaign has task steps)\n\nIf any step is a `task`, call `get_task_owners` **first**. If it returns one or more owners, every `task` step must carry a valid `ownerId` (400 otherwise). `taskType` depends on the connected CRM — for HubSpot use `CALL`, `EMAIL`, or `TODO`; don't invent variants like \"COLD_CALL\", use the closest standard value.\n\n### Phase 4 — Build the draft via `create_campaign`\n\nConstruct an ordered `steps` array (first action first) following the tool's input schema. Supported step types:\n- **`email`** — requires `subject` + `content`.\n- **`linkedin_connection`** — connection request. Add `waitForAcceptance` (`unit` must be `days`, `value` ≥ 1 integer) to branch on acceptance via `onAccepted` / `onNotAccepted`. Omit `waitForAcceptance` for fire-and-forget.\n- **`linkedin_message`** — `content`, `attachment`, or both (attachment-only is valid). `linkedin_message_bundle` sends several back-to-back.\n- **`linkedin_voice_message`** — text synthesized to speech; call `get_voices` first to get a valid `voiceId`. Optional `voiceSettings` (speed, stability, background).\n- **`linkedin_inmail`**, **`linkedin_visit_profile`**, **`linkedin_like_last_post`**, **`whatsapp_message`** (WhatsApp only where workspace + identity are configured).\n- **`condition`** — branch on lead data via `onTrue` / `onFalse`. Types include `has_professional_email`, `has_linkedin_profile`, `is_already_connected`, `has_been_contacted`, `email_opened`, `email_clicked`, `task_completed`, `connection_accepted` (the timed ones need a `waitFor` object), and `lead_field` (with `field` + `operator`).\n- **`task`** — manual task (see Phase 3).\n- **`add_to_another_campaign`** — route the lead into another campaign owned by the same workspace, then end the branch.\n- **`end`** — stop a branch explicitly.\n- Any step takes an optional `delay` (`value` + `unit`). Put shared follow-up work *after* a branching step rather than duplicating it inside both branches.\n\nSet campaign-level options as needed: `name` (required), `identityId`, `excludeContactedLeads`, `shouldAutomaticallySend`, tracking flags. `create_campaign` returns the campaign with an `appUrl` — surface it.\n\n### Phase 5 — Validate\n\n- Before creating, you can dry-run the payload with `validate_campaign` (validates the submitted body only, no campaign created). Validation errors use full paths like `steps[0].onTrue[1].subject`.\n- After creating, inspect the stored draft with `validate_campaign_draft` (by `campaignId`) — returns blocking errors plus non-blocking warnings and the campaign `appUrl`. Fix all blocking errors before assigning audience.\n\n### Phase 6 — Assign the audience\n\n- List → campaign: `add_a_contact_group_to_a_campaign` (`campaignId`, `contactGroupId`) creates conversations for every contact in the list. Find the list via `get_lists` (or build one with **build-targeted-lead-list**).\n- Single contact: `add_a_contact_to_a_campaign` (`campaignId`, `contactId`).\n- Both return a `campaignAppUrl` — surface it. Confirm the audience count with the user before activating.\n\n### Phase 7 — Activate (explicit confirmation required)\n\n**Never auto-activate.** State plainly what's about to happen (\"This will start sending to N contacts from identity X\"), and only on explicit user go call `update_campaign_status` with `status: ACTIVE` — this assigns identities/emails to conversations and starts sending. Return the campaign `appUrl`.\n\nStatus notes the user must understand:\n- There is **no PAUSED status.** `DRAFT` is a soft, best-effort pause — in-flight conversations keep progressing until they next re-evaluate status, so it does not instantly halt sends.\n- To reliably stop outreach to **one** contact right now, use `pause_a_contact_in_a_campaign`, not a status change.\n- `COMPLETED` and `DELETED` are terminal (a `COMPLETED` campaign can only move to `DELETED`). Don't set them casually.\n\n### Phase 8 — Iterate and monitor\n\n- To spin a variant of a winning campaign (A/B a new identity, new angle), use `clone_a_campaign` — it copies workflow, messages, tasks, tags, and folder; requires a target `identityId` and takes an optional `name`. Returns the clone's `appUrl`.\n- For performance tracking after launch, route to **campaign-performance-analyzer**.\n\n---\n\n## Enginy MCP tools used\n\n- `create_campaign` — build the draft from the public step model\n- `validate_campaign` — dry-run a proposed payload\n- `validate_campaign_draft` — validate the stored draft of a created campaign\n- `get_identities` — pick the sender identity\n- `get_task_owners` — required before `task` steps when owners exist\n- `get_voices` — valid `voiceId`s for `linkedin_voice_message`\n- `get_contact_field_metadata` — discover valid `{placeholder}` fields\n- `get_lists` — find the audience list\n- `add_a_contact_group_to_a_campaign` / `add_a_contact_to_a_campaign` — assign audience\n- `update_campaign_status` — activate (ACTIVE) or soft-pause (DRAFT), on explicit confirm only\n- `pause_a_contact_in_a_campaign` — reliably stop one contact\n- `clone_a_campaign` — duplicate a winner onto a new identity\n\n---\n\n## Important Notes\n\n- **Never activate or send without explicit user confirmation.** Present the plan, wait for go, then `update_campaign_status: ACTIVE`.\n- **`DRAFT` is not a hard stop.** For an instant stop of one contact use `pause_a_contact_in_a_campaign`; `COMPLETED`/`DELETED` are irreversible.\n- **`task` steps require `ownerId`** whenever `get_task_owners` returns owners; `taskType` must be a value the connected CRM accepts.\n- **`waitForAcceptance.unit` must be `days`, `value` an integer ≥ 1.** `onAccepted`/`onNotAccepted` are only valid when `waitForAcceptance` is set.\n- **Voice steps need a real `voiceId` from `get_voices`.**\n- **Placeholders are single-brace** and must be real fields (`get_contact_field_metadata`); `{previousMessage}`-style generics don't resolve.\n- **WhatsApp steps** need workspace + identity WhatsApp configuration.\n- **Always return `appUrl` / `campaignAppUrl`** so the user can open the campaign.\n- **Rate limits:** campaign writes 30 req/min.\n\n---\n\n## Examples\n\n**Example 1 — Email-first sequence, list audience**\nUser has copy and a list. → `get_identities` → pick sender → `create_campaign` with steps: `email` (opener) → `email` with `delay` 3 days (follow-up) → `condition` `email_opened` (waitFor 5 days) → onTrue: `email` bump; onFalse: `end` → `validate_campaign_draft` (clean) → `get_lists` to find the list → `add_a_contact_group_to_a_campaign` → tell user \"ready, N contacts, send from X — activate?\" → on yes, `update_campaign_status: ACTIVE` → return `appUrl`.\n\n**Example 2 — Multichannel with LinkedIn branch**\nSteps: `linkedin_connection` with `waitForAcceptance` 5 days → onAccepted: `linkedin_message` → wait 2 days → `email`; onNotAccepted: `email` only. → `get_voices` not needed (no voice step) → validate → assign single VIP contact via `add_a_contact_to_a_campaign` → confirm → activate.\n\n**Example 3 — Clone a winner**\nUser: \"Duplicate our best campaign for the second SDR's inbox.\" → `get_identities` to get the new seat's `identityId` → `clone_a_campaign` with that `identityId` and a new `name` → returns `appUrl` → user reviews copy → assign new audience → confirm → activate.\n\n---\n\n## Troubleshooting\n\n| Problem | Fix |\n|---|---|\n| `create_campaign` / `validate_campaign` 400 with a `steps[...]` path | Fix the field at that exact path (missing `subject`, bad `delay`, etc.) |\n| `task` step rejected | `get_task_owners` returns owners → add `ownerId`; or `taskType` isn't CRM-valid → use CALL/EMAIL/TODO |\n| `waitForAcceptance` rejected | `unit` must be `days` and `value` an integer ≥ 1 |\n| `onAccepted`/`onNotAccepted` rejected | Only valid when `waitForAcceptance` is set on that `linkedin_connection` step |\n| Voice step fails | `voiceId` isn't from `get_voices` — fetch and use a valid one |\n| Placeholder renders literally | Not a real field — verify via `get_contact_field_metadata`; don't use `{previousMessage}` |\n| Campaign won't stop after DRAFT | Expected — DRAFT is a soft pause; use `pause_a_contact_in_a_campaign` per contact |\n| Status transition 400 | Invalid transition (e.g. out of COMPLETED/DELETED) — those are terminal |\n| `add_a_contact_group_to_a_campaign` 404 | Campaign or contact group ID wrong — confirm via `get_lists` / campaign lookup |\n| `clone_a_campaign` 404 | Campaign or `identityId` not found — verify both |\n"
}SHA-256: 880a24b73fc03dc5797c6dc1c02d4ac700282ad66d54603eeeb871e5a05e0718