/install email-composer
Email Writer
Email composition and sending tool using OpenClaw browser automation.
Features
- Draft professional emails with AI assistance
- Reply to emails with context
- Support Gmail, Outlook, QQ Mail, 163 Mail
- Multi-language support (Chinese and English)
Trigger Conditions
- "Help me write an email"
- "Reply to this email"
- "Send email to someone"
- "Write a thank you email"
Privacy Warning
This skill accesses your email account through browser session.
- Reads and sends emails on your behalf
- Uses your browser email login session
- Only use on trusted email accounts
Step 1: Understand Email Request
Ask user for email details:
- Recipient email address
- Subject line
- Content/key points
- Formal or informal tone
- Language preference
Step 2: Detect Email Provider
// Detect which email provider based on URL
function detectProvider(url) {
if (url.includes('mail.google.com') || url.includes('gmail.com')) return 'gmail';
if (url.includes('outlook.live.com') || url.includes('outlook.com')) return 'outlook';
if (url.includes('mail.qq.com')) return 'qq';
if (url.includes('mail.163.com')) return '163';
if (url.includes('mail.126.com')) return '126';
return 'unknown';
}
Step 3: Open Email Client
// Open email client based on user preference
await browser.open({ url: "https://mail.google.com" })
await browser.wait({ timeout: 5000 })
Supported platforms:
- Gmail: mail.google.com
- Outlook: outlook.live.com
- QQ Mail: mail.qq.com
- 163 Mail: mail.163.com
- 126 Mail: mail.126.com
Step 4: Compose New Email
Gmail Compose
// Click compose button (Gmail)
await browser.click({ selector: '[gh="cm"]' })
await browser.wait({ timeout: 2000 })
// Fill recipient
await browser.evaluate((email) => {
const toField = document.querySelector('[name="to"]');
if (toField) {
toField.focus();
toField.value = email;
toField.dispatchEvent(new Event('input', { bubbles: true }));
}
}, recipientEmail)
// Fill subject
await browser.evaluate((subject) => {
const subjectField = document.querySelector('[name="subjectbox"]');
if (subjectField) {
subjectField.focus();
subjectField.value = subject;
}
}, emailSubject)
// Fill body
await browser.evaluate((body) => {
const bodyField = document.querySelector('[role="textbox"][contenteditable="true"]');
if (bodyField) {
bodyField.focus();
bodyField.innerHTML = body.replace(/\
/g, '\x3Cbr>');
}
}, emailBody)
// Send (Gmail)
await browser.click({ selector: '[gh="s"]' })
Outlook Compose
// Click new message button (Outlook)
await browser.click({ selector: '[aria-label="New message"], .ms-Button--primary' })
await browser.wait({ timeout: 2000 })
// Fill recipient (Outlook)
await browser.evaluate((email) => {
const toField = document.querySelector('[aria-label="To"], input[placeholder*="To"]');
if (toField) {
toField.focus();
toField.value = email;
toField.dispatchEvent(new Event('input', { bubbles: true }));
}
}, recipientEmail)
// Fill subject (Outlook)
await browser.evaluate((subject) => {
const subjectField = document.querySelector('[aria-label="Subject"], input[placeholder*="Subject"]');
if (subjectField) {
subjectField.focus();
subjectField.value = subject;
}
}, emailSubject)
// Fill body (Outlook)
await browser.evaluate((body) => {
const bodyField = document.querySelector('[aria-label="Message body"], [role="textbox"]');
if (bodyField) {
bodyField.focus();
bodyField.innerHTML = body.replace(/\
/g, '\x3Cbr>');
}
}, emailBody)
// Send (Outlook)
await browser.click({ selector: '[aria-label="Send"], button[title="Send"]' })
QQ Mail Compose
// Click compose button (QQ Mail)
await browser.click({ selector: '.compose-button, .btn-compose, [title="写信"]' })
await browser.wait({ timeout: 2000 })
// Fill recipient (QQ Mail)
await browser.evaluate((email) => {
const toField = document.querySelector('#to_input, input[name="to"]') ||
document.querySelector('[aria-label="收件人"]');
if (toField) {
toField.focus();
toField.value = email;
toField.dispatchEvent(new Event('input', { bubbles: true }));
}
}, recipientEmail)
// Fill subject (QQ Mail)
await browser.evaluate((subject) => {
const subjectField = document.querySelector('#subject, input[name="subject"]');
if (subjectField) {
subjectField.focus();
subjectField.value = subject;
}
}, emailSubject)
// Fill body (QQ Mail)
await browser.evaluate((body) => {
const bodyField = document.querySelector('#QMEditorArea iframe') ||
document.querySelector('.qmEditorArea');
if (bodyField) {
if (bodyField.tagName === 'IFRAME') {
bodyField.contentDocument.body.innerHTML = body.replace(/\
/g, '\x3Cbr>');
} else {
bodyField.innerHTML = body.replace(/\
/g, '\x3Cbr>');
}
}
}, emailBody)
// Send (QQ Mail)
await browser.click({ selector: '.btn-send, [title="发送"], .qmBtn' })
163 Mail Compose
// Click compose button (163 Mail)
await browser.click({ selector: '.nui-mainToolbar-btn, .js-component-composebtn' })
await browser.wait({ timeout: 2000 })
// Fill recipient (163 Mail)
await browser.evaluate((email) => {
const toField = document.querySelector('#draft_to_input, input[name="to"]') ||
document.querySelector('.nui-editableAddr-input');
if (toField) {
toField.focus();
toField.value = email;
toField.dispatchEvent(new Event('input', { bubbles: true }));
}
}, recipientEmail)
// Fill subject (163 Mail)
await browser.evaluate((subject) => {
const subjectField = document.querySelector('#draft_subject, input[name="subject"]');
if (subjectField) {
subjectField.focus();
subjectField.value = subject;
}
}, emailSubject)
// Fill body (163 Mail)
await browser.evaluate((body) => {
const bodyField = document.querySelector('.APP-editor-iframe iframe') ||
document.querySelector('.nuiEditor');
if (bodyField) {
if (bodyField.tagName === 'IFRAME') {
bodyField.contentDocument.body.innerHTML = body.replace(/\
/g, '\x3Cbr>');
} else {
bodyField.innerHTML = body.replace(/\
/g, '\x3Cbr>');
}
}
}, emailBody)
// Send (163 Mail)
await browser.click({ selector: '.nui-mainToolbar-sendBtn, .js-component-sendbtn' })
Step 5: Reply to Email
Gmail Reply
await browser.click({ selector: '.zA' })
await browser.click({ selector: '[aria-label="Reply"]' })
await browser.wait({ timeout: 2000 })
await browser.evaluate((body) => {
const bodyField = document.querySelector('[role="textbox"][contenteditable="true"]');
if (bodyField) bodyField.innerHTML = body.replace(/\
/g, '\x3Cbr>');
}, replyBody)
await browser.click({ selector: '[gh="s"]' })
Outlook Reply
await browser.click({ selector: '[aria-label="Reply"]' })
await browser.wait({ timeout: 2000 })
await browser.evaluate((body) => {
const bodyField = document.querySelector('[aria-label="Message body"]');
if (bodyField) bodyField.innerHTML = body.replace(/\
/g, '\x3Cbr>');
}, replyBody)
await browser.click({ selector: '[aria-label="Send"]' })
QQ Mail Reply
await browser.click({ selector: '.btn-reply, [title="回复"]' })
await browser.wait({ timeout: 2000 })
await browser.evaluate((body) => {
const iframe = document.querySelector('#QMEditorArea iframe');
if (iframe) iframe.contentDocument.body.innerHTML = body.replace(/\
/g, '\x3Cbr>');
}, replyBody)
await browser.click({ selector: '.btn-send, [title="发送"]' })
163 Mail Reply
await browser.click({ selector: '.js-component-replybtn, [title="回复"]' })
await browser.wait({ timeout: 2000 })
await browser.evaluate((body) => {
const iframe = document.querySelector('.APP-editor-iframe iframe');
if (iframe) iframe.contentDocument.body.innerHTML = body.replace(/\
/g, '\x3Cbr>');
}, replyBody)
await browser.click({ selector: '.js-component-sendbtn' })
Step 6: Send Confirmation
// Wait for send
await browser.wait({ timeout: 3000 })
// Check for success message
const sent = await browser.evaluate(() => {
const text = document.body.innerText;
return text.includes('Message sent') ||
text.includes('Sent') ||
text.includes('已发送') ||
text.includes('发送成功') ||
text.includes('邮件已发送');
})
if (sent) {
console.log("Email sent successfully!")
}
Email Templates
Thank You Email
Subject: Thank you for your support
Dear [Name],
Thank you for [specific reason].
[Details]
Best regards, [Your name]
Work Report
Subject: Work Report - [Date]
Hi Manager,
Weekly report:
- Completed tasks
- In progress
- Next week plan
Best, [Your name]
Meeting Invitation
Subject: Meeting: [Topic]
Hi [Name],
Please join our meeting:
Time: [Time] Location: [Location/Link] Agenda:
- Topic 1
- Topic 2
Please confirm your attendance.
Thanks, [Your name]
Error Handling
- Email not logged in: Prompt user to log in
- Send failed: Prompt user to send manually
- Attachment failed: Suggest manual attachment
- Unknown provider: Ask user which email service
Limitations
- Cannot handle email verification codes
- Cannot send encrypted emails
- No batch sending support
- QQ/163 may need iframe handling
Privacy and Security
- No email content uploaded externally
- All processing done locally
- Browser email session accessed
- Review before sending sensitive information
Notes
- Requires OpenClaw v2026.3.22+
- Gmail has most reliable selectors
- QQ/163 use iframe for email body
- Test with non-sensitive account first
- Make sure OpenClaw is installed (local or Docker)
- Run the install command in chat:
/install email-composer - After installation, invoke the skill by name or use
/email-composer - Provide required inputs per the skill's parameter spec and get structured output
What is Email Writer?
Email writer and sender for OpenClaw. Use when user needs to draft, reply, or send emails via browser. Supports Gmail, Outlook, QQ Mail, 163 Mail. Requires O... It is an AI Agent Skill for Claude Code / OpenClaw, with 400 downloads so far.
How do I install Email Writer?
Run "/install email-composer" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.
Is Email Writer free?
Yes, Email Writer is completely free, licensed under MIT-0. You can download, install and use it at no cost.
Which platforms does Email Writer support?
Email Writer is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).
Who created Email Writer?
It is built and maintained by ToBeWin (@tobewin); the current version is v1.0.4.