---
name: revor-outreach
description: Function-triggered outreach execution via Revor for LinkedIn, Email, and WhatsApp. Use this skill to detect outreach intent, validate sending prerequisites, draft channel-ready copy, and dispatch only after requirements are met and user confirmation is clear. If required recipient/contact data is missing, ask the user to provide it or search it first.
---

# Revor Outreach (Function-Triggered)

## 1) Goal

Treat this skill as an **outreach executor**, not a generic search workflow.

It should:
- detect outreach intent,
- validate Revor account/API/channel readiness,
- draft send-ready outreach copy by channel,
- dispatch via Revor API when conditions are satisfied,
- return a clear result summary (sent/failed/skipped with reasons).

If required recipient data is missing (`profile_url`, `address`, `phone`), **do not fabricate**. Ask the user to provide it or search it first.

---

## 2) Capabilities, Triggers, and Required Inputs

### Capability A — Outreach Intent Detection

**Trigger when any of these is requested:**
- “Send a LinkedIn invite/message”
- “Send a cold email”
- “Send a WhatsApp intro”
- “Use Revor to reach out”

**Required inputs:**
- at least one target contact,
- at least one target channel (LinkedIn / Email / WhatsApp).

If missing, ask for target + channel before continuing.

---

### Capability B — Pre-Send Readiness Check

**Trigger when:**
- user asks to send now / proceed with sending.

**All required conditions:**
1. User has a Revor account (with usable paid access if required by Revor plan/policy).
2. User has a valid API key.
3. User has connected at least one sendable channel account (LinkedIn / Email / WhatsApp).
4. For the requested channel, a connected account exists with `connected + can_send=true`.

**If missing, guide clearly:**
- Register/login: `https://revor.ai`
- API key page: `https://revor.ai/my-api-keys`
- Connect send accounts: `https://revor.ai/console/connect`

**Reminder style requirements:**
- Do **not** present setup as a rigid fixed sequence unless the user explicitly asks for step-by-step instructions.
- First explain in plain language **what is missing** and **why it matters**:
  - Revor account = access to the platform
  - connected LinkedIn/Email/WhatsApp account = the actual sending channel
  - API key = the permission key that allows the agent to dispatch through Revor
- Explicitly tell the user the expected variable name: `REVOR_API_KEY`, and clearly point to persistent config locations first:
  - OpenClaw/local persistent path: `~/.config/RevorSkill/.env`
  - Claude Desktop + MCP persistent config: target MCP server `env` in `claude_desktop_config.json`
  - Claude Code/CLI: persistent shell profile/env tooling (not a one-off session export)
- If the user does not want to configure it manually, offer to configure it directly for them at the correct path.
- Avoid framing this as temporary setup unless the user explicitly asks for temporary behavior.
- Use beginner-friendly language. Assume the user may have zero prior knowledge.
- Prefer a warm checklist style over terse technical warnings.
- If the user may be missing multiple prerequisites, say that clearly without blame, e.g. "You may still be missing one or more of these: account, connected sending channel, or API key."
- If the exact missing item is unknown, say so honestly and point the user to the 3 relevant places without pretending to know which one is incomplete.
- If the user is clearly a beginner, include short clarifiers such as:
  - "If you don't have a Revor account yet, register first."
  - "If you already have one, sign in and connect the channel you want to send from."
  - "Then create or copy an API key so I can send on your behalf."
  - "If you want, send the key here and I’ll configure `REVOR_API_KEY` for you in the persistent config path."
- Avoid sounding blocking or bureaucratic. The goal is to help the user understand readiness, not just reject the action.
- Only give a numbered sequence when the user asks for "一步一步", "step by step", or seems confused after a simpler reminder.

**Suggested reminder pattern:**

> I can help send this, but I don’t have sending access yet.
> Usually that means one or more of these is still missing: Revor account, connected sending channel, or API key.
>
> The key I need is `REVOR_API_KEY`.
> Recommended persistent config locations:
> - OpenClaw/local: `~/.config/RevorSkill/.env`
> - Claude Desktop MCP: target server `env` in `claude_desktop_config.json`
> - Claude Code/CLI: persistent shell profile/env tooling
>
> If you want, send me the key and I’ll configure it for you directly in the right place so new windows/sessions don’t need reconfiguration.
> Once that’s set, I can continue immediately.

---

### Capability C — Draft Outreach Copy (No Send Required)

**Trigger when:**
- user asks for outreach copy/draft,
- user does not clearly request immediate dispatch.

**Required inputs:**
- target role/company/objective,
- channel and language preference (default to user language if unspecified).

If information is incomplete, ask only the minimum necessary questions. If still incomplete, return a “missing fields” list.

**LinkedIn note constraint:**
- When drafting a LinkedIn connection note / invite note / add-note message, keep the final copy **within 200 characters** unless the user explicitly says they do not need a connection-note-safe version.
- Prefer concise, natural wording over squeezing in extra context.

---

### Capability D — Dispatch Outreach

**Trigger when:**
- user explicitly asks to send,
- pre-send checks pass,
- channel-specific recipient fields are complete.

**Channel-required fields:**
- LinkedIn: `recipient.profile_url` (the contact's **personal LinkedIn profile page URL**, e.g. their public `/in/...` profile link — not a search results page or company page)
- Email: `recipient.address`
- WhatsApp: `recipient.phone`

If any required field is missing:
- do not send,
- state exactly which field is missing,
- ask user to provide it or search it first.

---

## 3) Channel Requirements Matrix

| Channel | Required recipient field | Sendable when |
| --- | --- | --- |
| LinkedIn | `profile_url` (personal LinkedIn profile URL) | sendable LinkedIn account exists + profile URL present |
| Email | `address` | sendable Email account exists + email present |
| WhatsApp | `phone` | sendable WhatsApp account exists + phone present |

---

## 4) Environment & Secret Rules

Prefer persistent configuration so new windows/sessions do not require reconfiguration.

Resolve secrets in this order:
1. `~/.config/RevorSkill/.env` (OpenClaw/local persistent default)
2. Platform persistent config (e.g., Claude Desktop MCP server `env` in `claude_desktop_config.json`)
3. Process/runtime environment (session-scoped fallback)
4. `<current-skill-dir>/.env` (last fallback)

Platform examples:
- Claude Desktop + MCP: set `REVOR_API_KEY` in the MCP server `env` map (`claude_desktop_config.json`)
- Claude Code/CLI: keep `REVOR_API_KEY` in persistent shell/env tooling (avoid one-off exports when persistence is desired)

Required keys:

```bash
REVOR_BASE_URL="https://revor.ai"
REVOR_API_KEY=""
```

Rules:
- never store secret values in prompts, MEMORY.md, chat transcripts, or committed repo files,
- if `.env` is used and file does not exist, create `~/.config/RevorSkill/.env`,
- if keys are missing, add key names without overwriting existing values,
- if user provides a key in chat and asks for help, configure it directly in persistent config (default `~/.config/RevorSkill/.env`),
- verification checks are recommended but should stay lightweight and non-blocking,
- never echo full API keys in outputs; mask as `****`.

---

## 5) API Usage (Do Not Change Endpoints)

Use the public Revor API as an asynchronous job API. A successful write request returns `202 Accepted` with a job id; it does **not** mean the message or action has already completed. Always check the job result with `GET /api/v1/outreach/jobs/{id}`.

Base URL:

```txt
https://revor.ai
```

Authentication:

```http
Authorization: Bearer <REVOR_API_KEY>
```

`x-api-key: <REVOR_API_KEY>` is also supported, but prefer `Authorization: Bearer`.

### 5.1 List Sendable Connected Accounts

```http
GET /api/v1/connect/accounts?channel=<email|linkedin|whatsapp>&can_send=true
Authorization: Bearer <REVOR_API_KEY>
```

Select an account only when:

```json
{
  "status": "ok",
  "can_send": true
}
```

Also verify that `channel` matches the requested channel. Do not require an exact `lifecycle_status` string; use it as diagnostic context because deployments may expose values such as `active`, `connected`, or `reconnect_required`.

If no matching account is found:
- do not send,
- explain that a sendable channel account is missing,
- direct the user to `https://revor.ai/console/connect`.

Fastest smoke test:

```http
GET /api/v1/connect/accounts
Authorization: Bearer <REVOR_API_KEY>
```

Expected behavior:
- `200` -> key works and account listing is allowed
- `401` -> key missing / invalid / revoked / expired
- `403` -> key does not have `connect.accounts:read`
- `429` -> key, user, or pre-auth rate limit was exceeded

Example success shape:

```json
{
  "ok": true,
  "request_id": "req_xxx",
  "items": [
    {
      "account_id": "connect_account_xxx",
      "channel": "email",
      "name": "sales@example.com",
      "account_identifier": "sales@example.com",
      "status": "ok",
      "lifecycle_status": "active",
      "can_send": true,
      "can_receive": true
    }
  ]
}
```

### 5.2 Dispatch Outreach

```http
POST /api/v1/outreach/dispatches
Content-Type: application/json
Idempotency-Key: <unique-key>
Authorization: Bearer <REVOR_API_KEY>
```

Required top-level fields:

| Field | Required | Notes |
| --- | --- | --- |
| `account_id` | yes | Use `items[].account_id` from a sendable account. |
| `channel` | yes | `email`, `linkedin`, or `whatsapp`. Must match the account channel. |
| `action` | yes | Always `outreach`. |
| `recipient` | yes | Channel-specific recipient data. |
| `content` | yes | Channel-specific message content. |

Optional scheduling fields:
- `min_channel_task_interval_seconds`
- `minChannelTaskIntervalSeconds`
- `min_user_task_interval_seconds`

The interval is clamped to `60..86400` seconds and applies per user + channel.
For planned or batch outreach, generate a randomized `min_channel_task_interval_seconds` for each task instead of reusing a fixed value. Use `180..360` seconds as the default recommended range unless the user gives a stricter cadence. Never send a value below `60`; Revor will clamp it to `60`.

#### Minimal channel payload patterns

When generating payloads for multiple planned outreach tasks, include `min_channel_task_interval_seconds` in each payload and randomize it per task, preferably within `180..360`. The minimal examples below omit scheduling only to show the channel-specific required fields.

Email requires `recipient.address`, `content.subject`, and at least one of `content.text` / `content.html`:

```json
{
  "account_id": "connect_account_xxx",
  "channel": "email",
  "action": "outreach",
  "recipient": {
    "address": "prospect@example.com",
    "name": "Prospect Name"
  },
  "content": {
    "subject": "Quick introduction",
    "text": "Hi, this is a quick API test."
  }
}
```

LinkedIn requires `recipient.profile_url` and `content.text` or an attachment:

```json
{
  "account_id": "connect_account_xxx",
  "channel": "linkedin",
  "action": "outreach",
  "recipient": {
    "profile_url": "https://www.linkedin.com/in/test-user/"
  },
  "content": {
    "text": "Hi, this is a LinkedIn API test."
  }
}
```

WhatsApp requires `recipient.phone` and `content.text` or an attachment:

```json
{
  "account_id": "connect_account_xxx",
  "channel": "whatsapp",
  "action": "outreach",
  "recipient": {
    "phone": "+14155550123"
  },
  "content": {
    "text": "Hi, this is a WhatsApp API test."
  }
}
```

LinkedIn behavior:
- already connected -> resolves to `direct_message`
- not connected or unknown -> resolves to `invitation`

Successful create response:

```json
{
  "ok": true,
  "request_id": "req_xxx",
  "item": {
    "id": "job_uuid",
    "status": "queued",
    "action": "outreach.dispatch",
    "channel": "email",
    "scheduled_at": "2026-05-20T10:00:00.000Z"
  }
}
```

### 5.3 Like a Relevant LinkedIn Post

```http
POST /api/v1/outreach/linkedin/post-likes
Content-Type: application/json
Idempotency-Key: <unique-key>
Authorization: Bearer <REVOR_API_KEY>
```

Required fields:
- `profile_url` or `profileUrl`
- `content`, `topic`, or `intent`

Optional fields:
- `account_id` or `accountId`
- `post_limit` or `postLimit` (default `20`, max `50`)
- `locale` (`en` or `zh`)
- `min_channel_task_interval_seconds`

Use the same randomized interval policy as outreach dispatches for repeated LinkedIn like tasks: prefer `180..360` seconds per task, with an absolute minimum of `60` seconds.

If the user has exactly one usable LinkedIn account, `account_id` may be omitted. If multiple usable LinkedIn accounts exist, pass `account_id`.

Payload:

```json
{
  "account_id": "connect_account_xxx",
  "profile_url": "https://www.linkedin.com/in/test-user/",
  "content": "AI sales automation and outbound workflow",
  "post_limit": 20,
  "locale": "en"
}
```

Behavior:
- fetches recent LinkedIn posts,
- picks the most relevant post using the internal selector,
- likes at most one post,
- returns `result.status = "skipped"` if no relevant post is found.

### 5.4 Check Outreach Job

```http
GET /api/v1/outreach/jobs/<job_uuid>
Authorization: Bearer <REVOR_API_KEY>
```

Terminal statuses:
- `succeeded`
- `failed`
- `cancelled`

Non-terminal statuses:
- `queued`
- `scheduled`
- `running`

Successful job example:

```json
{
  "ok": true,
  "request_id": "req_xxx",
  "item": {
    "id": "job_uuid",
    "status": "succeeded",
    "action": "outreach.dispatch",
    "channel": "email",
    "attempt_count": 1,
    "result": {
      "status": "accepted",
      "message_id": "message_xxx"
    },
    "error": null
  }
}
```

Failed job example:

```json
{
  "ok": true,
  "request_id": "req_xxx",
  "item": {
    "id": "job_uuid",
    "status": "failed",
    "action": "outreach.dispatch",
    "channel": "linkedin",
    "attempt_count": 2,
    "result": null,
    "error": {
      "code": "connect_account_reconnect_required",
      "message": "connect_account_reconnect_required",
      "retryable": false
    }
  }
}
```

Important: job execution failure still returns HTTP `200` from the job query endpoint. Treat `item.status = "failed"` as the failure signal.

---

## 6) Dispatch Policy

- Default: draft first, then send after clear user confirmation.
- Send immediately only when user explicitly requests immediate dispatch.
- Never guess private contact data.
- Prefer small, high-quality contact sets over bulk blasts.

---

## 7) Error Handling (User-Friendly)

### 401

> Revor authentication failed. Please verify the API key is present, correct, active, and not expired or revoked.

### 403

> This API key does not have sufficient permissions. Please update key permissions or create a new key.

### 404

For `account_not_found`:

> The selected sending account was not found for this Revor user. Please list Connect accounts again and choose a current sendable account.

For `external_api_job_not_found`:

> I could not find that Revor job for this API key. Please verify the job id and make sure it was created under the same Revor user.

### 409

> Revor could not proceed because of a conflict, such as an idempotency mismatch, account/channel mismatch, reconnect-required account, or temporarily unusable sending account.

### 429

> Revor rate limited this request. Wait for `Retry-After` if present, then retry the same business action with the same `Idempotency-Key`.

### 503

> Revor accepted the request path, but the async worker queue is temporarily unavailable. Retry later with the same `Idempotency-Key`.

### Job failed

When `GET /api/v1/outreach/jobs/{id}` returns `ok: true` and `item.status = "failed"`, treat it as an execution failure, not an HTTP request failure.

> Revor created the job, but the job failed during execution: `<item.error.code>`.

---

## 8) Output Templates

### 8.1 Dispatched / Queued

```md
Outreach job created:

- Contact: <name>
- Company: <company>
- Channel: <linkedin|email|whatsapp>
- Job ID: <job_uuid>
- Job status: <queued|scheduled|running|succeeded|failed|cancelled>

Draft/message:
> <message text>

Revor response:
- request_id: <id>
- action: <outreach.dispatch|outreach.linkedin.post_like>
- scheduled_at: <timestamp|null>

Next step:
- I will check the job result with `GET /api/v1/outreach/jobs/<job_uuid>` until it reaches a terminal status.
```

### 8.2 Completed

```md
Outreach job completed:

- Contact: <name>
- Channel: <linkedin|email|whatsapp>
- Final status: <succeeded|failed|cancelled>
- Job ID: <job_uuid>

Result:
- resolved_action: <message|direct_message|invitation|post_like|skipped>
- message_id: <id|null>
- reason: <reason|null>

Revor response:
- request_id: <id>
```

### 8.3 Not Sent (Missing Data)

```md
Not sent yet. Missing required data:
- Channel: <linkedin|email|whatsapp>
- Missing field(s): <profile_url/address/phone>

Please provide these fields, or search for them first, then I can continue immediately.
```

---

## 9) Compliance Boundaries

Refuse assistance for:
- harassment,
- phishing,
- impersonation,
- fraud,
- unreviewed mass spam outreach,
- fabricated identities or contact details.

Keep outreach professional, truthful, and auditable.
