UNPKG

@agentled/cli

Version:

CLI for Agentled — manage workflows, apps, and knowledge from the command line. Zero context-window cost for AI agents.

124 lines (102 loc) 5.56 kB
# Email Workflows — Triggers, Dedup & the Composed Email Step > Loaded on demand from the Agentled skill. Read this for any workflow that > reads or sends email. Pairs with the `08-composed-email-approval` pattern > (`agentled examples 08-composed-email-approval`). ## Trigger choice: polling vs event **Default to Schedule trigger + label-based dedup** for all email intake workflows (deal flow, triage, review, digest). Only propose an App Event trigger when the user explicitly needs sub-minute latency. | User asks for | Trigger | |---------------|---------| | "process inbound emails", "triage daily", "review pitches" | **Schedule** (polling) | | "as soon as", "real-time", "within X seconds/minutes" | **App event** | ## Canonical email polling pattern ``` schedule triggerGMAIL_FETCH_EMAILS (-label:processed newer_than:1d) → loop: [process] → GMAIL_ADD_LABEL (mark processed) → milestone ``` Step order: 1. **`GMAIL_CREATE_LABEL`** — create/get the `processed` label (idempotent, returns label ID) 2. **`GMAIL_FETCH_EMAILS`** — query `-label:processed newer_than:1d` (or wider window as needed) 3. **Loop** — process each email (AI analysis, KG storage, enrichment, etc.) 4. **`GMAIL_ADD_LABEL`** — apply `{{steps.ensure-label.id}}` to mark email done (dedup gate) ## Label ID rule (prevents `400: Invalid label`) Gmail requires **label IDs** (e.g., `Label_3456789012345`), not display names (e.g., `"processed"` or `"agentled"`). **Always resolve via `GMAIL_CREATE_LABEL`** and reference its returned `id`: ```json { "stepInputData": { "label_id": "{{steps.ensure-label.id}}" } } ``` Never pass a string label name directly to `GMAIL_ADD_LABEL`. See `docs/workflows/triggers.md` for the full decision framework, query examples, and common mistakes. --- ## Email Step Pattern (AI Draft → Approve → Send) Email steps use a single `aiAction` step (never separate "draft" + "gmail send" appAction steps). The AI drafts the email, a human approves, then the platform sends it. ### 1. Outreach Profile Input Page When a workflow sends emails, add an outreach profile input page to `context.inputPages` so the user can configure sender identity: ```json { "title": "Outreach Profile", "pathname": "outreach-profile", "configuration": { "contextKey": "outreachProfile", "shortDescriptionFields": ["name", "fromEmail"], "fields": [ { "name": "name", "label": "Sender Name", "type": "text", "required": true }, { "name": "fromEmailLabel", "label": "From Name", "type": "text", "required": true }, { "name": "fromEmail", "label": "From Email", "type": "connected_emails_selector_multiple", "required": true }, { "name": "replyToEmail", "label": "Reply-To Email (optional)", "type": "text" } ] } } ``` ### 2. Composed Email Step ```json { "id": "send_email", "type": "aiAction", "name": "Send Email", "pipelineStepPrompt": { "type": "email", "template": "Draft a personalized email...\n{{steps.previous_step.data}}\nFormat the body as email-safe HTML with one <p> per visual paragraph.\nReturn JSON ONLY per schema.", "responseStructure": { "email": { "from": "{{context.outreachProfile.fromEmail}}", "to": "recipient@example.com", "subject": "Email subject line", "body": "Email body (email-safe HTML)", "bodyType": "html" } }, "responseType": "json" }, "renderer": { "type": "Email", "config": { "fromContextKey": "outreachProfile" } }, "onApproval": { "action": "schedule-email", "executedText": "Email sent by {{name}} at {{date}}", "scheduledText": "Email scheduled to be sent for {{date}} by {{name}}", "failedText": "Email failed to send." }, "integrations": [{ "type": "oneOf", "label": "Email", "connectorType": "email", "options": [ { "name": "Gmail", "url": "https://gmail.com", "isUserAccountConnectionRequired": true }, { "name": "Outlook", "url": "https://outlook.com", "isUserAccountConnectionRequired": true } ], "selectionHint": "preferConnected" }], "creditCost": 10, "next": { "conditions": { "approvalRequired": true } } } ``` ### Key Requirements - **Always** include `outreachProfile` input page when using email - `pipelineStepPrompt.type: "email"` — tells the system this is an email step - `renderer.config.fromContextKey: "outreachProfile"` — links renderer to sender profile - `onApproval.action: "schedule-email"` — triggers the actual send; without it, approval does nothing - `next.conditions.approvalRequired: true` — blocks the pipeline until human approval - Email body must be email-safe HTML (`<p>`, `<br>`, `<a>`, `<strong>` — no CSS, no scripts) - Preserve readable email structure in `email.body`: use separate `<p>` blocks for the greeting, each paragraph, links/CTA, and sign-off; do not return the whole email as one plain-text paragraph, and do not rely on newline-only formatting because email clients collapse it. - **Never** use separate "draft" + "gmail send" appAction steps for outreach - Set an output page `displayConfig.executionNameTemplate` that names the entity and the outreach state. For single-entity outreach, use `{{entityName}} - Email Drafted` before approval and `{{entityName}} - Email Sent` or `{{entityName}} - Contacted` after the post-send status step, e.g. `Joe Dan - VC Firm Name - Email Drafted`; add `{{today}}` only when a short date helps. For batch runs, summarize counts instead, e.g. `26 May - 4 sourced - 3 contacted`.