email-resend

ClawSkills 作者 clawskills v1.0.14

Send and receive emails using the Resend API. Use for: (1) sending emails directly via Resend API, (2) receiving email notifications via cron, (3) drafting replies with proper threading, (4) downloading attachments from inbound emails. **Required env vars:** RESEND_API_KEY (API key). **Optional env vars:** DEFAULT_FROM_EMAIL, DEFAULT_FROM_NAME (fall back to preferences file if not set). **License: Apache-2.0** — See LICENSE file for details.

安装 / 下载方式

TotalClaw CLI推荐
totalclaw install clawskills:clawskills~ivelin-email-resend
cURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/clawskills%3Aclawskills~ivelin-email-resend/file -o ivelin-email-resend.md
## License

**Apache License 2.0** — See LICENSE file for full text.

# Email via Resend

Send and receive emails using the Resend API.

## Configuration

**No config file needed.** The skill auto-discovers settings from:

1. **Environment variables** — RESEND_API_KEY (required), DEFAULT_FROM_EMAIL/NAME (optional)
2. **Preferences file** — `memory/email-preferences.md` (from_email, from_name, telegram target)
3. **OpenClaw context** — channel, chat_id, thread_id (for cron delivery)

### Required Environment Variables

```bash
export RESEND_API_KEY="re_123456789"        # Resend API key (required)
# DEFAULT_FROM_EMAIL and DEFAULT_FROM_NAME are optional - loaded from preferences file if not set
```

### Preferences File

The skill reads sender info from `memory/email-preferences.md`:

```yaml
---
from_email: you@company.com
from_name: Your Name
telegram:
  target: "CHAT_ID"
  threadId: "THREAD_ID"
---
```

Scripts check env vars first, then fall back to preferences file.

### First-Time Setup

When the skill is first invoked, the sub-agent should:

1. **Check context** — OpenClaw context already has:
   - `context.user.email` (from USER.md)
   - `context.channel` (from current session)
   - `context.chat_id`
   - `context.thread_id` (for topics)

2. **Check memory** — Use `memory_get` tool:
   - Try: `memory_get path="memory/email-preferences.md"`
   - If not found, ask user to create `memory/email-preferences.md` (NO fallback scanning)

3. **If missing, ask user** — Via chat message (IMPORTANT for cron jobs):
   - "Which email should I send from?" (from_email)
   - "What's your display name for sent emails?" (from_name)
   - "Which channel/topic should I notify you on?" (telegram target + threadId)
   
   Then create `memory/email-preferences.md` with their answers using the format above.

4. **Commit to memory** — Write preferences to persist across sessions:
   ```bash
   write path="memory/email-preferences.md" content="---
   from_email: $EMAIL
   from_name: $NAME
   telegram:
     target: \"$CHAT_ID\"
     threadId: \"$THREAD_ID\"
   ---

   # Email Notification Preferences
   Saved auto-configured
   "
   ```
   This ensures memory_get finds it in future sessions. Use MD format with YAML frontmatter.

**Format (MD with YAML frontmatter):**
```markdown
---
from_email: you@company.com
from_name: Your Name
telegram:
  target: \"123456789\"
  threadId: \"334\"
---

# Email Notification Preferences
- **Updated:** 2026-01-01
- **Purpose:** Default notification channel for email alerts
```
**Important:** Store in `memory/email-preferences.md` (NOT MEMORY.md) - isolated cron jobs can read this file via memory_get but NOT MEMORY.md.

### Context Fields (Available in Sub-Agent)

| Field | Source | Example |
|-------|--------|---------|
| `user.email` | USER.md | `you@company.com` |
| `user.name` | USER.md | `Your Name` |
| `channel` | OpenClaw | from context |
| `chat_id` | OpenClaw | `123456789` |
| `thread_id` | OpenClaw | `334` |

The skill uses these directly from OpenClaw context — no parsing needed.

## Usage

## Inbound (Receive)

### Cron Setup

There are two ways to configure the cron:

#### Option 1: Static (Hardcoded Target)

Use this if you always want the same delivery target:

```bash
openclaw cron add \
  --name "email-resend-inbound" \
  --cron "*/15 * * * *" \
  --message "Follow instructions in skills/email-resend/cron-prompts/email-inbound.md exactly. If new emails found, include them in your reply." \
  --session isolated \
  --announce \
  --channel telegram \
  --to "-1003748898773:topic:334"
```

#### Option 2: Dynamic (From Preferences) — Recommended

This reads your notification preferences from `memory/email-preferences.md` and configures the cron automatically.

Run:

```bash
python3 ~/.openclaw/workspace/skills/email-resend/scripts/configure-cron.py
```

What it does:
1. Reads `memory/email-preferences.md` for your telegram target/threadId
2. Deletes any existing `email-resend-inbound` cron
3. Creates a new cron with your preferred delivery target

**First-time setup:** If preferences don't exist, it will tell you what to configure.

**Parameters:**
- `--schedule "cron */15 * * * *"` — Run every 15 minutes
- `--session isolated` — Required for agentTurn payloads
- `--announce` — Enable delivery of results to chat
- `--channel telegram` — Delivery channel
- `--to` — Telegram target (format: `chat_id:topic:thread_id`)

**Note:** The cron prompt reads notification preferences from `memory/email-preferences.md`. On first run, if preferences are missing, it will ask you for:
- Which channel for notifications (telegram, discord, etc.)
- Chat ID and Thread ID (for topics)

### Manual Check

```bash
python3 ~/.openclaw/workspace/skills/email-resend/scripts/inbound.py
```

### Notification Format

Each new email triggers a notification with:
- From, Subject, Date
- Body preview (~2000 chars)
- Attachment list (if any)
- Importance: 🔥 HIGH / 📅 MEETING / 📬 NORMAL

### Acknowledge Flow (CRITICAL)

**NEVER auto-acknowledge emails.** Only the user can acknowledge by:
- Replying to the notification message, OR
- Typing: `done` / `ack`

Emails must remain in pending state until user explicitly acknowledges.

Use `draft-reply.py` to compose replies with proper quoting.

**Important:** Always use inline replies (`[[reply_to_current]]`) to keep messages linked in the thread. This enables:
- Proper custody chain tracking
- Reply-to-email tracing
- Better conversation flow

**CRITICAL:** When responding via OpenClaw message tool, use `replyTo` parameter (not `[[reply_to_current]]` tag):
```python
message(action="send", channel="<from-context>", replyTo="<msg_id>", ...)
```

## Scripts

| Script | Purpose |
|--------|---------|
| `inbound.py` | Check emails, send notifications |
| `draft-reply.py` | Draft reply workflow with quoting & threading |
| `outbound.py` | Send emails directly |
| `download_attachment.py` | Download attachments from inbound emails |

### Downloading Attachments

To download attachments from an inbound email:

```bash
# List attachments (shows IDs)
python3 scripts/download_attachment.py <email_id> --list

# Download all to directory
python3 scripts/download_attachment.py <email_id> --output-dir ./attachments

# Download specific attachment
python3 scripts/download_attachment.py <email_id> --attachment-id <attachment_id>
```

**Note:** The API path is `/emails/receiving/{email_id}/attachments` (not the standard `/emails/` path).

## State Files

- `memory/email-resend-inbound-notified.json` — pending/acknowledged emails
- `memory/email-message-map.json` — notification message_id → email_id (legacy)
- `memory/email-custody-chain.json` — Full DAG of email → notification → actions
- `memory/email-msg-to-chain.json` — notification message_id → chain lookup
- `memory/email-draft-state.json` — Active draft state (email_id, status, reply_content)

See `docs/custody-chain.md` for DAG design.

## Outbound (Send)

```bash
python3 ~/.openclaw/workspace/skills/email-resend/scripts/outbound.py \
  --to "recipient@example.com" \
  --subject "Hello" \
  --body "Message text"

# With attachments
python3 ~/.openclaw/workspace/skills/email-resend/scripts/outbound.py \
  --to "recipient@example.com" \
  --subject "Here's the file" \
  --body "See attachment" \
  --attachment ./file.pdf \
  --attachment ./image.png
```

## ⚠️ CRITICAL: Email Threading Rule (2026-02-22)

**MANDATORY: Always use `draft-reply.py` for replying to emails.**

This is non-negotiable. Failure to follow this rule will result in broken Gmail threading.

### Why This Matters
- Gmail threads emails based on `In-Reply-To` AND `References` headers
- Using wrong headers = reply appears as NEW thread = context lost
- There's no way to fix this after sending

### ✅ Correct Workflow (ALWAYS USE THIS)

```bash
# Step 1: Start draft (fetches Message-ID automatically)
python3 ~/.openclaw/workspace/skills/email-resend/scripts/draft-reply.py start <email_id>

# Step 2: Set reply c