| name | email-autopilot |
| description | Auto-detect unanswered emails, draft contextual follow-ups, and send on schedule. Scans sent emails, identifies stale threads by age, cross-references calendar for context, and generates personalized follow-ups that match the original tone. |
Email Autopilot
Automatically detect emails that need follow-up, draft context-aware responses, and send them on your behalf. No more leads falling through the cracks.
When to Use
- "Check my inbox for emails that need follow-up"
- "Draft follow-ups for all unanswered emails from this week"
- "Which of my sent emails haven't gotten a reply?"
- "Set up a daily follow-up routine for my outbox"
- "Follow up on all sales emails from the past 5 days"
Required Context (ask if not provided)
- Time window: How far back to scan (default: 7 days)
- Email categories to include: All sent, or filtered (e.g., only sales outreach, only meeting requests)
- Tone preference: Match original tone (default), or specify (casual, formal, direct)
- Max follow-ups per thread: Default 3 (never exceed this)
Prerequisites
- Gmail access connected (gmail_write scope required for sending; gmail_read for scan-only mode)
- Google Calendar access recommended (for meeting cross-reference)
Connect Services (run once)
await requestApproval({
reason: "Need Gmail access to scan sent emails and send follow-ups",
type: "service_auth",
payload: { provider: "google", service: "gmail_write" }
})
await requestApproval({
reason: "Need Calendar access to check if meetings are scheduled with contacts",
type: "service_auth",
payload: { provider: "google", service: "calendar" }
})
Safety Rules (CRITICAL)
- Maximum 20 follow-ups per session. Never exceed this.
- Maximum 3 follow-ups per thread. After 3 unanswered follow-ups, stop.
- Wait 30-60 seconds between sends to avoid Gmail rate limits.
- ALWAYS present all drafts for user approval before sending anything.
- Never follow up on weekends or outside 8am-6pm in the recipient's likely timezone.
- Skip threads where recipient replied (even with "no thanks" or "not interested").
- Skip promotional/marketing emails and internal team threads.
- Never use "just bumping this" or "per my last email" or "circling back" without adding new value.
Workflow A: Scan & Detect Stale Threads
Step 1: Query Sent Emails
var daysBack = 7
var sent = await google.gmail.listMessages({
query: "in:sent newer_than:" + daysBack + "d",
maxResults: 50
})
console.log("Found " + sent.count + " sent emails in the past " + daysBack + " days")
Step 2: Check Each Thread for Replies
For each sent email, determine if the recipient has replied:
var staleThreads = []
for (var i = 0; i < sent.messages.length; i++) {
var msg = await google.gmail.getMessage(sent.messages[i].id)
if (msg.to.includes("noreply") || msg.to.includes("no-reply")) continue
if (msg.to.includes("unsubscribe")) continue
var threadMessages = await google.gmail.listMessages({
query: "thread:" + msg.threadId,
maxResults: 10
})
var recipientReplied = false
var userLastSentDate = null
for (var j = 0; j < threadMessages.messages.length; j++) {
var threadMsg = await google.gmail.getMessage(threadMessages.messages[j].id)
if (threadMsg.from.includes(msg.to.split("@")[0])) {
if (new Date(threadMsg.date) > new Date(msg.date)) {
recipientReplied = true
break
}
}
}
if (!recipientReplied) {
var daysSinceSent = Math.floor((Date.now() - new Date(msg.date).getTime()) / (1000 * 60 * 60 * 24))
staleThreads.push({
messageId: msg.id,
threadId: msg.threadId,
to: msg.to,
subject: msg.subject,
date: msg.date,
daysSince: daysSinceSent,
snippet: msg.snippet,
originalBody: msg.body
})
}
}
console.log("Stale threads found: " + staleThreads.length)
Step 3: Classify Staleness Level
for (var thread of staleThreads) {
if (thread.daysSince <= 2) {
thread.level = "TOO_EARLY"
thread.action = "Skip - too early to follow up"
} else if (thread.daysSince <= 4) {
thread.level = "WARM"
thread.action = "Gentle nudge - brief, friendly reminder"
} else if (thread.daysSince <= 7) {
thread.level = "STANDARD"
thread.action = "Standard follow-up - add new information or reframe"
} else if (thread.daysSince <= 14) {
thread.level = "COLD"
thread.action = "Final follow-up - break-up style, leave door open"
} else {
thread.level = "DEAD"
thread.action = "Archive - too old to follow up naturally"
}
}
var actionableThreads = staleThreads.filter(t =>
t.level === "WARM" || t.level === "STANDARD" || t.level === "COLD"
)
console.log("Actionable threads: " + actionableThreads.length)
Step 4: Cross-Reference Calendar (Optional)
for (var thread of actionableThreads) {
var contactName = thread.to.split("@")[0].replace(/[._]/g, " ")
var events = await google.calendar.listEvents({
query: contactName,
timeMin: new Date().toISOString(),
timeMax: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
})
if (events.length > 0) {
thread.skipReason = "Meeting scheduled: " + events[0].summary + " on " + events[0].start
thread.action = "Skip - meeting already scheduled"
}
}
actionableThreads = actionableThreads.filter(t => !t.skipReason)
Step 5: Present Stale Thread Report
Present a clean table to the user:
## Stale Email Report (Past 7 Days)
| # | Recipient | Subject | Days | Level | Suggested Action |
|---|-----------|---------|------|-------|-----------------|
| 1 | jane@acme.com | Re: Partnership proposal | 5 | STANDARD | Add new data point + reframe ask |
| 2 | bob@widget.co | Demo follow-up | 3 | WARM | Gentle reminder + offer flexibility |
| 3 | mike@startup.io | Intro from Sarah | 9 | COLD | Break-up style, leave door open |
Skipped: 2 threads (meeting already scheduled), 4 threads (too early), 1 thread (dead)
Workflow B: Draft Context-Aware Follow-Ups
Step 1: Analyze Original Thread
For each actionable thread, read the full conversation:
var drafts = []
for (var thread of actionableThreads) {
var originalMsg = await google.gmail.getMessage(thread.messageId)
var analysis = {
type: "unknown",
tone: "professional",
whatWasAsked: "",
keyContext: ""
}
var bodyLower = originalMsg.body.toLowerCase()
if (bodyLower.includes("demo") || bodyLower.includes("meeting") || bodyLower.includes("call") || bodyLower.includes("chat")) {
analysis.type = "meeting_request"
} else if (bodyLower.includes("price") || bodyLower.includes("pricing") || bodyLower.includes("cost") || bodyLower.includes("proposal")) {
analysis.type = "sales_outreach"
} else if (bodyLower.includes("question") || bodyLower.includes("wondering") || bodyLower.includes("could you") || bodyLower.includes("can you")) {
analysis.type = "question"
} else if (bodyLower.includes("intro") || bodyLower.includes("connect") || bodyLower.includes("nice to meet")) {
analysis.type = "networking"
} else if (bodyLower.includes("invoice") || bodyLower.includes("payment") || bodyLower.includes("due")) {
analysis.type = "business_admin"
} else {
analysis.type = "general"
}
if (bodyLower.includes("hey") || bodyLower.includes("hi!") || bodyLower.includes("hope you")) {
analysis.tone = "casual"
} else if (bodyLower.includes("dear") || bodyLower.includes("sincerely") || bodyLower.includes("regards")) {
analysis.tone = "formal"
}
thread.analysis = analysis
}
Step 2: Generate Follow-Up Drafts
Draft follow-ups based on staleness level and email type:
WARM follow-up (3-4 days):
Structure: 2-3 sentences. Reference original. Add small new value.
Meeting request: "Hi [Name], wanted to make sure my note didn't get buried.
Still happy to find a time that works - even a quick 15 min would be great.
[If calendar available: Here are a few slots that work: ...]"
Sales/general: "Hi [Name], following up on my note about [topic].
[One new piece of info: recent stat, relevant news about their company, or helpful resource].
Happy to chat whenever works for you."
STANDARD follow-up (5-7 days):
Structure: 2-3 sentences. Different angle from original. Must add NEW information.
Meeting request: "Hi [Name], circling back on this. Totally understand if timing
isn't right. In case it helps, [share a relevant resource, case study, or insight
that gives them a reason to respond]. Would [specific day/time] work?"
Sales/general: "Hi [Name], wanted to share something that might be useful regardless.
[New data point, case study, or industry insight relevant to their role].
If [original topic] is still on your radar, happy to discuss."
COLD follow-up (8-14 days):
Structure: 2 sentences max. Break-up style. No pressure.
All types: "Hi [Name], I know things get busy - just wanted to close the loop.
If [topic] isn't a priority right now, totally understand.
The door's always open if it comes up later."
Step 3: Present Drafts for Approval
## Follow-Up Drafts (3 emails)
### 1. Jane Doe (jane@acme.com) - STANDARD (5 days)
**Original subject:** Re: Partnership proposal
**Original context:** Asked about potential integration partnership
**Draft:**
> Hi Jane, following up on the partnership idea. I put together a quick
> one-pager showing how similar integrations have worked for [comparable company].
> Thought it might be useful context. Would Thursday or Friday work for a 15-min call?
[ ] Approve [ ] Edit [ ] Skip
### 2. Bob Smith (bob@widget.co) - WARM (3 days)
**Original subject:** Demo follow-up
**Original context:** Offered to schedule a product demo
**Draft:**
> Hey Bob, just making sure this didn't get lost in the shuffle.
> Happy to do a quick 15-min walkthrough whenever works - even async
> via a recorded demo if that's easier. What works best?
[ ] Approve [ ] Edit [ ] Skip
Workflow C: Send Approved Follow-Ups
Step 1: Batch Send with Rate Limiting
var approved = drafts.filter(d => d.approved)
console.log("Sending " + approved.length + " follow-up emails")
await requestApproval({
reason: "Send " + approved.length + " follow-up emails to: " +
approved.map(d => d.to).join(", ")
})
for (var i = 0; i < approved.length; i++) {
var draft = approved[i]
await google.gmail.sendMessage({
to: draft.to,
subject: "Re: " + draft.originalSubject,
body: draft.followUpBody,
threadId: draft.threadId,
inReplyTo: draft.originalMessageId,
references: draft.originalMessageId
})
console.log("Sent " + (i + 1) + "/" + approved.length + ": " + draft.to)
if (i < approved.length - 1) {
var delay = 30 + Math.floor(Math.random() * 30)
console.log("Waiting " + delay + " seconds before next send...")
await wait({ waitTime: delay })
}
}
Step 2: Log to Tracking Sheet
var sheetTitle = "Email Follow-Up Tracker"
var trackingData = approved.map(d => [
new Date().toISOString().slice(0, 10),
d.to,
d.originalSubject,
d.level,
d.followUpNumber,
"Sent",
""
])
await google.sheets.appendValues({
spreadsheetId: trackerId,
range: "Sheet1!A1",
values: trackingData
})
console.log("Tracking sheet updated with " + trackingData.length + " entries")
Step 3: Schedule Next Check
After sending follow-ups, suggest scheduling the next scan:
## Follow-Up Complete
Sent: 3 emails
Skipped: 2 (user chose to skip)
Suggested next actions:
- Check for replies in 2-3 days
- Threads with Follow-Up #1 sent today will be eligible for Follow-Up #2 in 5 days
- Consider scheduling a daily 8am workflow: "Scan inbox and draft follow-ups"
Want me to set up a daily cron workflow for this?
Workflow D: Scheduled Daily Scan (Cron Mode)
When running as a scheduled workflow (e.g., daily at 8am):
Automatic Mode
- Scan sent emails from past 7 days
- Identify stale threads (skip TOO_EARLY and DEAD)
- Cross-reference calendar
- Draft follow-ups for all actionable threads
- Present drafts for user approval (never auto-send in cron mode)
- Wait for user to approve/edit/skip each draft
- Send approved drafts
- Update tracking sheet
Cron Setup Example
Follow-Up Quality Checklist
Before approving any draft, verify:
Error Handling
- Gmail rate limit hit: Stop immediately, report to user, suggest retrying in 1 hour
- Thread not found: Skip and log, may have been deleted
- Calendar access denied: Continue without calendar cross-reference, note in report
- Send failure: Log the error, skip to next email, report failures at end