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 addressescc(array of strings, optional, default: []) — CC recipientsbcc(array of strings, optional, default: []) — BCC recipientssubject(string, required) — email subject linebody(string, required) — email body contentattachments(array of strings, optional, default: []) — file paths to attachtemplate(string, optional) — template name for variable substitutiontemplate_vars(dict, optional) — variables to substitute in template
- Outputs:
draft_id(string) — unique identifier for the draftstatus(string) — "pending_review"created_at(string) — ISO 8601 timestampvalidation_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 displayrecipient_count(integer) — total recipients (to + cc + bcc)recipient_domains(array) — unique domains in recipient listattachment_count(integer) — number of attachmentstotal_attachment_size(string) — human-readable total sizewarnings(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 approveuser_confirmation(boolean, required) — explicit user approval
- Outputs:
draft_id(string) — the approved draft IDstatus(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 draftsmtp_config(string, optional) — path to SMTP config file (default: config/email.yaml)
- Outputs:
success(boolean) — whether the email was sentmessage_id(string) — SMTP message IDsent_at(string) — ISO 8601 timestamprecipients_accepted(array) — addresses that accepted deliveryrecipients_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 draftfile_path(string, required) — absolute path to the file to attach
- Outputs:
success(boolean) — whether attachment was addedfile_name(string) — name of attached filefile_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 templatevariables(dict, required) — key-value pairs for substitutionto(array of strings, required) — recipient addresses
- Outputs:
draft_id(string) — ID of the created draftstatus(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
# config/email.yaml (chmod 600)
smtp:
host: smtp.gmail.com
port: 587
use_tls: true
username: ${EMAIL_USER} # From environment
password: ${EMAIL_PASS} # App password, not account password
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:
- Draft was created and reviewed before any send attempt
- All recipient addresses passed format validation
- User explicitly approved the draft before send
- SMTP connection used TLS/SSL encryption
- No credentials appear in logs or output
- Delivery status was confirmed (accepted vs rejected recipients)
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
