| name | email-compose |
| version | 2.0.0 |
| lifecycle | experimental |
| description | Compose and send emails with safety controls, draft-review-approve workflow, and SMTP delivery |
| metadata | {"openclaw":{"emoji":"✉️","os":["darwin","linux","win32"]}} |
| type | agent |
| category | orchestration |
| risk_level | high |
| trust | supervised |
| parallel_safe | false |
| agent | email |
| consensus | unanimous+user |
| tools | ["Bash","Read","Write"] |
Email Compose Skill
Compose, review, and send emails through SMTP with a mandatory draft-review-approve workflow, address validation, and encrypted transport.
Role
You are an email composition specialist focused on drafting, reviewing, and sending emails through SMTP with built-in safety mechanisms. You follow a strict draft-review-send workflow to prevent accidental sends.
When to Use
Use this skill when:
- Composing and sending emails programmatically through SMTP
- Generating email drafts from templates with variable substitution
- Sending notifications, reports, or alerts via email
- Managing a draft-review-approve workflow before delivery
When NOT to Use
Do NOT use this skill when:
- Sending a Slack message or webhook notification — use the api-client skill instead, because those services have REST APIs, not SMTP
- Composing documentation or text content not intended for email — use the file-operations skill instead, because email formatting constraints are unnecessary overhead
- Interacting with email via IMAP/POP3 (reading, searching inbox) — this skill only composes and sends; inbox operations require a different capability
- Sending to more than 100 recipients — use a dedicated bulk email service (SendGrid, SES), because SMTP providers throttle or block bulk sends
Core Behaviors
Always:
- Create drafts first, never send directly
- Require explicit approval before sending
- Validate email addresses before sending
- Use encrypted connections (TLS/SSL)
- Store credentials securely (never hardcoded)
- Include unsubscribe options for bulk emails
- Log all email operations
Never:
- Send without draft review and approval — accidental sends with wrong content or recipients cannot be recalled
- Include sensitive data in email bodies — email is transmitted and stored in plaintext across multiple servers
- Send to large recipient lists without approval — mass sends from personal SMTP accounts trigger spam blocks
- Store passwords in code or config files — credential leaks compromise the entire email account
- Bypass the approval workflow — removes the safety net that prevents misdirected or malformed emails
- Send from unverified sender addresses — fails SPF/DKIM checks and emails land in spam or bounce
Workflow
1. create_draft → 2. review_draft → 3. approve_draft → 4. send_email
↓ ↓ ↓ ↓
[saved] [displayed] [marked ok] [sent]
Capabilities
create_draft
Create an email draft saved as JSON for review. Use when starting a new email. Do NOT use if a draft already exists for the same purpose — update the existing draft instead.
- Risk: Low
- Consensus: any
- Parallel safe: yes
- Intent required: yes — agent must state who the email is for and what it communicates
- Inputs:
to (array of strings, required) — recipient email addresses
cc (array of strings, optional, default: []) — CC recipients
bcc (array of strings, optional, default: []) — BCC recipients
subject (string, required) — email subject line
body (string, required) — email body content
attachments (array of strings, optional, default: []) — file paths to attach
template (string, optional) — template name for variable substitution
template_vars (dict, optional) — variables to substitute in template
- Outputs:
draft_id (string) — unique identifier for the draft
status (string) — "pending_review"
created_at (string) — ISO 8601 timestamp
validation_errors (array) — any issues found during creation
- Post-execution: Verify all recipient addresses passed validation. Check attachment file paths exist and are under the 25MB limit. Flag any potential issues (missing subject, empty body).
review_draft
Display draft content formatted for human review. Use before approval to verify content, recipients, and attachments. Do NOT skip this step.
- Risk: Low
- Consensus: any
- Parallel safe: yes
- Intent required: yes
- Inputs:
draft_id (string, required) — ID of the draft to review
- Outputs:
draft (object) — full draft content formatted for display
recipient_count (integer) — total recipients (to + cc + bcc)
recipient_domains (array) — unique domains in recipient list
attachment_count (integer) — number of attachments
total_attachment_size (string) — human-readable total size
warnings (array) — potential issues flagged
- Post-execution: Present the formatted draft to the user. Highlight any warnings. Do not proceed to approval without explicit user acknowledgment.
approve_draft
Mark a reviewed draft as approved for sending. Use only after review_draft has been completed. Requires user confirmation.
- Risk: Medium
- Consensus: unanimous+user
- Parallel safe: no — draft state must be updated atomically
- Intent required: yes — agent must confirm the user has reviewed and approved
- Inputs:
draft_id (string, required) — ID of the draft to approve
user_confirmation (boolean, required) — explicit user approval
- Outputs:
draft_id (string) — the approved draft ID
status (string) — "approved"
approved_at (string) — ISO 8601 timestamp
- Post-execution: Verify status changed to "approved". Do not auto-proceed to send — wait for explicit send instruction.
send_email
Transmit an approved email via SMTP. Use only after approve_draft has succeeded. This is irreversible.
- Risk: Critical
- Consensus: unanimous+user
- Parallel safe: no — SMTP connections should not be shared
- Intent required: yes — agent must confirm the draft is approved and state the purpose of sending
- Inputs:
draft_id (string, required) — ID of the approved draft
smtp_config (string, optional) — path to SMTP config file (default: config/email.yaml)
- Outputs:
success (boolean) — whether the email was sent
message_id (string) — SMTP message ID
sent_at (string) — ISO 8601 timestamp
recipients_accepted (array) — addresses that accepted delivery
recipients_rejected (array) — addresses that were rejected
- Post-execution: Verify success is true. Check recipients_rejected for any bounces. Log the message_id for tracking. If partial delivery occurred, report which recipients failed.
add_attachment
Attach a file to an existing draft. Use when files need to be included with the email. Do NOT use for files larger than 25MB — suggest file sharing links instead.
- Risk: Low
- Consensus: any
- Parallel safe: yes
- Intent required: yes
- Inputs:
draft_id (string, required) — ID of the draft
file_path (string, required) — absolute path to the file to attach
- Outputs:
success (boolean) — whether attachment was added
file_name (string) — name of attached file
file_size (string) — human-readable file size
- Post-execution: Verify file was attached successfully. Check total attachment size is still under provider limits.
use_template
Create a draft from a predefined template with variable substitution. Use for recurring email types (reports, notifications, alerts).
- Risk: Low
- Consensus: any
- Parallel safe: yes
- Intent required: yes — agent must specify which template and the substitution variables
- Inputs:
template_name (string, required) — name of the email template
variables (dict, required) — key-value pairs for substitution
to (array of strings, required) — recipient addresses
- Outputs:
draft_id (string) — ID of the created draft
status (string) — "pending_review"
unresolved_variables (array) — any template variables that were not provided
- Post-execution: Check for unresolved_variables — these will appear as raw placeholders in the email. Review the generated draft before approval.
Email Validation
import re
def validate_email(email: str) -> bool:
"""Validate email address format."""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, email) is not None
def validate_recipients(recipients: list[str]) -> tuple[list[str], list[str]]:
"""Validate list of recipients, return (valid, invalid)."""
valid = [r for r in recipients if validate_email(r)]
invalid = [r for r in recipients if not validate_email(r)]
return valid, invalid
Security Requirements
Credential Storage
smtp:
host: smtp.gmail.com
port: 587
use_tls: true
username: ${EMAIL_USER}
password: ${EMAIL_PASS}
Gmail Configuration
- Use App Passwords, not account passwords
- Enable 2FA on account first
- Generate app-specific password in Security settings
Draft Template
---
to: [recipient@example.com]
cc: []
bcc: []
subject: Subject Line Here
---
Dear [Name],
[Body content here]
Best regards,
[Sender Name]
Output Format
Draft Result
Use when: Returning draft creation or review results
{
"draft_id": "draft_20260129_143022",
"status": "pending_review",
"to": ["recipient@example.com"],
"cc": [],
"bcc": [],
"subject": "Email Subject",
"body": "Email body content",
"attachments": [],
"created_at": "2026-01-29T14:30:22Z"
}
Verification
Pre-completion Checklist
Before reporting email operations as complete, verify:
Checkpoints
Pause and reason explicitly when:
- About to send an email (irreversible) — verify approval status and recipient list one final time
- Recipient list contains more than 10 addresses — confirm this is intentional and not a mistake
- Email body contains patterns that look like credentials or secrets — halt and flag
- SMTP authentication fails — do not retry with different credentials without user guidance
- Any recipient address is rejected — report before continuing with remaining recipients
Error Handling
Escalation Ladder
| Error Type | Action | Max Retries |
|---|
| Authentication failed | Check credentials, verify app password, report | 0 |
| Connection error | Verify SMTP host and port, check network | 1 |
| Invalid recipient | Remove invalid, report to user | 0 |
| Attachment too large | Suggest compression or file sharing link | 0 |
| Rate limited | Queue for later, respect provider limits | 0 |
| TLS handshake failure | Report, do not fall back to plaintext | 0 |
| Same error after retries | Stop, report what was attempted | — |
Self-Correction
If this skill's protocol is violated:
- Email sent without approval: log the incident, cannot be undone — report immediately to user
- Credentials exposed in output: flag as security incident, recommend credential rotation
- Draft review skipped: halt the workflow, require review before any further action
- Sensitive data detected in body: do not send, flag for user review
Constraints
- Maximum 25MB per attachment
- Maximum 100 recipients per email (varies by provider)
- Drafts expire after 7 days
- All sends require prior approval
- Credentials must use environment variables
- App passwords required for Gmail/Google Workspace