Batch-create InboxMate demos for CRM prospects. Queries Twenty CRM for companies without opportunities, validates their websites, creates demos for valid ones, and marks unreachable/outdated ones as DISQUALIFIED.
All required tokens are in the .env file in the current working directory (the agenthub repo root). Read it at startup to get:
PSQUARED_CRM_TOKEN — Bearer token for Twenty CRM GraphQL APINUXT_MCP_DEMO_TOKEN — Bearer token for the InboxMate MCP serverOPENBRAND_API_KEY — API key for OpenBrand brand color/logo extractionDo this first: Read .env from the current directory and extract these values. If any are missing, stop and ask the user.
Before starting, ask the user for the offer deadline:
When should the demo offers expire? This sets the countdown timer on all demo pages in this batch. Examples: "in 14 days", "2026-04-01", "end of month"
Wait for the answer. Convert to an ISO 8601 date. Pass this deadline to every demo created in the batch — do NOT ask again per company.
Announce:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ InboxMate Batch Demo Pipeline Offer deadline: [date] Reading .env for CRM and MCP tokens... Querying CRM for unprocessed prospects... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Read PSQUARED_CRM_TOKEN from .env and use it to query the Twenty CRM (GraphQL at https://crm.psquared.dev/graphql) for companies that do NOT yet have an opportunity. Use this approach:
The remaining companies are unprocessed prospects.
Announce:
Found [N] unprocessed prospects.
If there's a specific company or list the user wants to process, use that instead.
For each prospect, before running the demo pipeline:
Use WebFetch on the company's domain (from CRM domainName.primaryLinkUrl).
Skip the prospect if ANY of these are true:
Do NOT ask the user what to do. Auto-skip and mark in CRM immediately.
First, check if the Company object has a field like idealCustomerProfile (ICP) or a rating/status field you can use. Run this introspection query once at the start:
query { __type(name: "Company") { fields { name type { name kind ofType { name } } } } }
If there's a usable boolean/enum field (e.g. idealCustomerProfile): set it to false or the "not a fit" value to mark the company directly.
Regardless, also create a DISQUALIFIED opportunity so the company is excluded from future batch runs:
mutation CreateOpportunity($data: OpportunityCreateInput!) {
createOpportunity(data: $data) { id name stage }
}
Variables:
{
"data": {
"name": "[Company] — Website not suitable",
"stage": "DISQUALIFIED",
"companyId": "[companyId]"
}
}
Announce for each skip:
SKIP: [Company] — [reason]Never pause to ask. Just mark and move to the next prospect.
Prospects with a working, current website proceed to Step 3.
Announce:
[N] prospects ready for demo creation, [M] skipped.
For each valid prospect, invoke the /inboxmate-demo skill (or follow the inboxmate-demo SKILL.md pipeline).
Process one prospect at a time — do not parallelize MCP calls.
After each demo is created:
SCREENING / PENDING_REVIEW with the demo URL — verify it was createdAnnounce after each:
DONE: [Company] — [playgroundUrl]
After all prospects are processed:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
BATCH COMPLETE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Demos created: [N]
- [Company 1] → [url]
- [Company 2] → [url]
Skipped (website issues): [M]
- [Company A] — [reason]
- [Company B] — [reason]
Already processed (had opportunity): [K]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
| Signal | Action |
|---|---|
| HTTP error / timeout | SKIP — "unreachable" |
| Parked / expired domain | SKIP — "parked domain" |
| "Coming soon" / "Under construction" | SKIP — "placeholder page" |
| No real content (just logo + contact form) | SKIP — "no meaningful content" |
| Copyright year 2+ years behind current | SKIP — "outdated (copyright YYYY)" |
| Broken images, dead links, 90s design | SKIP — "outdated/abandoned" |
| Social media profile only | SKIP — "no website (social only)" |
| No domain in CRM | SKIP — "no domain on file" |
| Working site with real content | PROCEED with demo |