3 minutes
Email-Drafting Personal Assistant
Introduction
I spend too much time crafting emails—whether it’s polite follow-ups, detailed updates, or quick replies. I decided to build a local assistant to draft and summarize emails directly on my laptop. No more worrying about corporate emails floating to third-party services, and I get custom templates that match my style.
Why a Local Email Assistant?
- Corporate Privacy: My emails, recipients, and content stay within my network.
- Personal Style: I can tweak the prompt to always include my signature, preferred greetings, and tone.
- Efficiency: Turn bullet points into polished paragraphs and auto-summarize long threads.
Pipeline Overview
- Email Data Ingestion – Pull emails via IMAP or read local
.eml
files. - Summarization & Context Extraction – Summarize long threads for context.
- Prompt Templates – Define generic templates for reply, follow-up, new email.
- Generation & Send – Call local LLM, review draft, and send via SMTP.
I’ll run through each component with code snippets and my personal workflow notes.
1. Email Data Ingestion
I use imaplib
to fetch recent emails or a local directory of .eml
files for testing.
import imaplib
import email
IMAP_SERVER = 'imap.company.com'
USERNAME = 'me@company.com'
PASSWORD = 'password'
# Connect and fetch
mail = imaplib.IMAP4_SSL(IMAP_SERVER)
mail.login(USERNAME, PASSWORD)
mail.select('inbox')
typ, data = mail.search(None, 'UNSEEN')
email_ids = data[0].split()
raw_emails = []
for eid in email_ids:
typ, msg_data = mail.fetch(eid, '(RFC822)')
raw_emails.append(email.message_from_bytes(msg_data[0][1]))
# Or read .eml files locally
# raw_emails = [email.message_from_file(open(path)) for path in os.listdir('emails/')]
print(f"Fetched {len(raw_emails)} new emails.")
I parse sender, date, subject, and the concatenated thread text for summarization.
2. Summarization & Context Extraction
Why: I need a quick summary of long threads so the assistant knows what’s happened so far.
from transformers import pipeline
summarizer = pipeline('summarization', model='facebook/bart-large-cnn', device=0)
def summarize_thread(email_messages):
full_thread = '\n'.join([msg.get_payload(decode=True).decode('utf-8', errors='ignore')
for msg in email_messages])
summary = summarizer(full_thread, max_length=100, min_length=20, do_sample=False)
return summary[0]['summary_text']
# Example
thread_summary = summarize_thread(raw_emails)
print("Thread Summary:\n", thread_summary)
I sometimes chunk very long threads, but usually bart-large-cnn
handles up to ~3k tokens fine.
3. Prompt Templates
I keep a set of Jinja2 templates for different scenarios:
-- reply.template --
Subject: Re: {{ subject }}
Hi {{ recipient_name }},
{{ body }}
Thanks,
{{ my_name }}
In code:
from jinja2 import Template
template_str = open('reply.template').read()
tmpl = Template(template_str)
prompt_input = tmpl.render(
subject=subject,
recipient_name='Alice',
body=thread_summary,
my_name='Me'
)
I swap out body
with bullet lists or questions depending on follow-up vs. new mail.
4. Generation & Sending
I send the prompt to a text-generation pipeline and then hand off to smtplib
after review.
from transformers import pipeline
import smtplib
from email.mime.text import MIMEText
generator = pipeline('text-generation', model='gpt2-medium', device=0)
# Generate draft
draft = generator(prompt_input, max_length=300)[0]['generated_text']
print("Draft Email:\n", draft)
# After manual review... send
msg = MIMEText(draft)
msg['Subject'] = f"Re: {subject}"
msg['From'] = USERNAME
msg['To'] = recipient_email
smtp = smtplib.SMTP('smtp.company.com', 587)
smtp.starttls()
smtp.login(USERNAME, PASSWORD)
smtp.send_message(msg)
smtp.quit()
I usually print the draft, edit minor tweaks, then press Enter to send from a small CLI wrapper.
Wrapping Up
This local email assistant saves me hours of repetitive writing and keeps all my sensitive content in-house. Next, I’ll experiment with integrating Firefox or Thunderbird plugins so I can draft directly in my mail client.