Creazione di un semplice agente assistente email
Dall’inizio alla fine: lettura della posta in arrivo → riepilogo → bozza della risposta → attesa dell’approvazione
Creazione di un semplice agente assistente email è una lezione AI Agents gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento AI Agents, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AI Agents include 4 lezioni in totale.
Architettura di un agente assistente email
Un agente assistente email segue una pipeline standard: Recuperare → Classificare → Decidere → Preparare una bozza → Approvare → Inviare. L'agente legge le email non lette, utilizza un LLM per classificarle e preparare le risposte, quindi attende l'approvazione di una persona prima di inviarle. Questo approccio con supervisione umana evita gli errori costosi dell'invio completamente autonomo delle email.
# Email Assistant Pipeline:
#
# 1. FETCH: Pull unread emails from Gmail API
# 2. CLASSIFY: LLM labels each email
# - 'action_needed': requires a reply
# - 'fyi': informational, no reply needed
# - 'spam': should be archived
# 3. DRAFT: LLM generates reply for 'action_needed' emails
# 4. APPROVE: Human reviews drafts in Gmail UI
# 5. SEND: Agent sends approved drafts
#
# Tools: Gmail API, Anthropic/OpenAI API, json, base64
print('Email assistant pipeline: fetch -> classify -> draft -> approve -> send')Definire gli strumenti dell'agente
Strutturi l'agente con definizioni chiare degli strumenti. Ogni strumento è una funzione Python con una responsabilità specifica. Questa modularità rende l'agente facile da testare, analizzare e ampliare con nuove funzionalità.
class EmailAssistantTools:
def __init__(self, gmail_service, llm_client):
self.gmail = gmail_service
self.llm = llm_client
def fetch_unread(self, max_emails=10):
'''Fetch unread emails from inbox.'''
pass
def classify_email(self, subject, body, sender):
'''Ask LLM to classify: action_needed / fyi / spam.'''
pass
def draft_reply(self, subject, body, sender, context):
'''Ask LLM to draft a reply to an email.'''
pass
def create_draft(self, message_id, reply_text):
'''Save draft reply in Gmail for human review.'''
pass
def send_approved_drafts(self):
'''Send all drafts marked as approved.'''
pass
print('Tool-based architecture enables testing each step independently')Passaggio 1: recuperare e analizzare le email
Il primo strumento recupera le email non lette ed estrae i campi principali di cui l'LLM ha bisogno: oggetto, mittente e corpo in testo semplice. Mantenga il corpo breve: limiti il testo ai primi 2000 caratteri per rispettare i limiti del contesto dell'LLM e ridurre i costi delle API.
import base64
def fetch_emails_for_classification(gmail_service, max_emails=10):
messages_list = gmail_service.users().messages().list(
userId='me',
q='is:unread label:inbox',
maxResults=max_emails
).execute().get('messages', [])
emails = []
for ref in messages_list:
msg = gmail_service.users().messages().get(
userId='me', id=ref['id'], format='full'
).execute()
headers = {h['name'].lower(): h['value']
for h in msg['payload'].get('headers', [])}
body = extract_plain_text_body(msg)[:2000] # trim for LLM
emails.append({
'id': msg['id'],
'thread_id': msg['threadId'],
'from': headers.get('from', ''),
'subject': headers.get('subject', '(no subject)'),
'body': body
})
print(f'Fetched {len(emails)} unread emails')
return emailsPassaggio 2: classificazione con LLM
Invii ogni email a un LLM per la classificazione. Utilizzi un prompt strutturato che chieda al modello di restituire un oggetto JSON con la categoria e il ragionamento. Richieda esplicitamente un output JSON: è più affidabile rispetto all'analisi del testo libero.
import json
import anthropic
import os
client = anthropic.Anthropic(api_key=os.environ['ANTHROPIC_API_KEY'])
def classify_email(subject, sender, body):
prompt = (
'Classify this email as exactly one of: action_needed, fyi, spam.\n'
'Return JSON only: {"category": "...", "reason": "..."}\n\n'
f'From: {sender}\n'
f'Subject: {subject}\n\n'
f'Body:\n{body[:1500]}'
)
response = client.messages.create(
model='claude-opus-4-5',
max_tokens=200,
messages=[{'role': 'user', 'content': prompt}]
)
try:
result = json.loads(response.content[0].text)
return result.get('category', 'fyi'), result.get('reason', '')
except json.JSONDecodeError:
return 'fyi', 'Could not parse LLM response'Passaggio 3: generare bozze di risposta
Per le email classificate come action_needed, chieda all'LLM di generare una bozza di risposta. Fornisca il contesto sul ruolo e sul tono dell'agente. Chieda una risposta professionale ma concisa e includa un segnaposto per qualsiasi informazione che la persona debba completare.
def draft_reply(subject, sender, body, agent_context):
prompt = (
'You are an email assistant drafting a professional reply.\n'
'Guidelines:\n'
'- Be concise and professional\n'
'- Use [FILL IN] for any info you don\'t know\n'
'- Start with a greeting, end with a sign-off\n\n'
f'Context about the recipient\'s work: {agent_context}\n\n'
f'Original email from {sender}:\n'
f'Subject: {subject}\n\n'
f'{body[:1500]}\n\n'
'Draft a reply:'
)
response = client.messages.create(
model='claude-opus-4-5',
max_tokens=500,
messages=[{'role': 'user', 'content': prompt}]
)
return response.content[0].text.strip()Passaggio 4: salvare le bozze per la revisione umana
Non invii mai email generate dall'IA senza una revisione umana. Le salvi come bozze di Gmail, così la persona può aprire Gmail, esaminarle, modificarle se necessario e inviarle. L'API per la creazione delle bozze è la stessa dell'API di invio, ma utilizza drafts().create().
import base64
from email.mime.text import MIMEText
def save_draft_reply(gmail_service, original_message, reply_text):
sender_header = next(
(h['value'] for h in original_message['payload'].get('headers', [])
if h['name'].lower() == 'from'), ''
)
subject = next(
(h['value'] for h in original_message['payload'].get('headers', [])
if h['name'].lower() == 'subject'), ''
)
msg_id_header = next(
(h['value'] for h in original_message['payload'].get('headers', [])
if h['name'].lower() == 'message-id'), ''
)
mime_msg = MIMEText(reply_text, 'plain', 'utf-8')
mime_msg['To'] = sender_header
mime_msg['Subject'] = 'Re: ' + subject
mime_msg['In-Reply-To'] = msg_id_header
mime_msg['References'] = msg_id_header
raw = base64.urlsafe_b64encode(mime_msg.as_bytes()).decode()
draft = gmail_service.users().drafts().create(
userId='me',
body={'message': {'raw': raw, 'threadId': original_message['threadId']}}
).execute()
print(f'Draft saved: {draft["id"]}')
return draft['id']
# --- demo: minimal stand-in for the Gmail API's service object ---
class _Exec:
def __init__(self, result):
self._result = result
def execute(self):
return self._result
class _FakeDrafts:
def create(self, userId, body):
return _Exec({'id': 'r9000abc'})
class _FakeUsers:
def drafts(self):
return _FakeDrafts()
class _FakeGmailService:
def users(self):
return _FakeUsers()
original_message = {
'threadId': 'thread_1',
'payload': {'headers': [
{'name': 'From', 'value': 'customer@example.com'},
{'name': 'Subject', 'value': 'Question about my order'},
{'name': 'Message-ID', 'value': '<abc123@mail.example.com>'}
]}
}
save_draft_reply(_FakeGmailService(), original_message, 'Thanks for reaching out, we will look into it.')
Passaggio 5: contrassegnare le email elaborate
Dopo aver elaborato un'email (classificata e con una bozza creata oppure archiviata), la contrassegni per impedire che venga elaborata nuovamente. Aggiunga un'etichetta personalizzata come AgentProcessed e rimuova l'etichetta UNREAD. Crei l'etichetta una sola volta, se non esiste.
def get_or_create_label(gmail_service, label_name):
labels = gmail_service.users().labels().list(userId='me').execute()
for label in labels.get('labels', []):
if label['name'] == label_name:
return label['id']
# Create the label
new_label = gmail_service.users().labels().create(
userId='me',
body={
'name': label_name,
'labelListVisibility': 'labelShow',
'messageListVisibility': 'show'
}
).execute()
print(f'Created label: {label_name}')
return new_label['id']
def mark_processed(gmail_service, message_id, agent_label_id):
gmail_service.users().messages().modify(
userId='me',
id=message_id,
body={
'addLabelIds': [agent_label_id],
'removeLabelIds': ['UNREAD']
}
).execute()
# --- demo: minimal stand-in for the Gmail API's service object ---
class _Exec:
def __init__(self, result):
self._result = result
def execute(self):
return self._result
class _FakeUsers:
def __init__(self):
self._labels = [{'id': 'Label_1', 'name': 'Processed'}]
def labels(self):
return self
def list(self, userId):
return _Exec({'labels': self._labels})
def create(self, userId, body):
print(f'Created label: {body["name"]}')
return _Exec({'id': 'Label_2', 'name': body['name']})
def messages(self):
return self
def modify(self, userId, id, body):
print(f'Marked {id} processed with {body}')
return _Exec({'id': id})
class _FakeGmailService:
def users(self):
return _FakeUsers()
gmail_service = _FakeGmailService()
label_id = get_or_create_label(gmail_service, 'Processed')
print(f'Label id: {label_id}')
new_label_id = get_or_create_label(gmail_service, 'AgentHandled')
mark_processed(gmail_service, 'msg_99', new_label_id)
Ciclo completo di esecuzione dell'agente
Colleghi tutti i passaggi in un unico metodo run(). L'agente scorre le email, classifica ciascuna di esse, crea bozze per gli elementi che richiedono un'azione, archivia lo spam e contrassegna tutte le email come elaborate. Al termine, registri un riepilogo.
def run_email_agent(gmail_service, agent_context, max_emails=10):
processed_label = get_or_create_label(gmail_service, 'AgentProcessed')
emails = fetch_emails_for_classification(gmail_service, max_emails)
summary = {'action_needed': 0, 'fyi': 0, 'spam': 0, 'drafts_created': 0}
for email in emails:
category, reason = classify_email(
email['subject'], email['from'], email['body']
)
summary[category] += 1
print(f'[{category}] {email["subject"][:60]} - {reason[:50]}')
if category == 'action_needed':
# Fetch full message for reply context
full_msg = gmail_service.users().messages().get(
userId='me', id=email['id'], format='full'
).execute()
reply = draft_reply(
email['subject'], email['from'],
email['body'], agent_context
)
save_draft_reply(gmail_service, full_msg, reply)
summary['drafts_created'] += 1
mark_processed(gmail_service, email['id'], processed_label)
print('\nAgent run complete:')
for k, v in summary.items():
print(f' {k}: {v}')
return summaryGestione degli errori e resilienza
Inserisca ogni passaggio di elaborazione delle email in un blocco try/except. Un errore relativo a un'email non deve mai impedire all'agente di elaborare le restanti. Registri gli errori con l'ID e l'oggetto dell'email, così potrà esaminarli in seguito.
from googleapiclient.errors import HttpError
def process_email_safely(gmail_service, email, agent_context, label_id):
try:
category, reason = classify_email(
email['subject'], email['from'], email['body']
)
if category == 'action_needed':
full_msg = gmail_service.users().messages().get(
userId='me', id=email['id'], format='full'
).execute()
reply = draft_reply(
email['subject'], email['from'],
email['body'], agent_context
)
save_draft_reply(gmail_service, full_msg, reply)
mark_processed(gmail_service, email['id'], label_id)
return category
except HttpError as e:
print(f'Gmail API error on {email["id"]}: {e.resp.status}')
return 'error'
except Exception as e:
print(f'Unexpected error on {email["subject"][:50]}: {e}')
return 'error'Pianificare l'esecuzione periodica dell'agente
Esegua l'agente email secondo una pianificazione utilizzando un semplice ciclo con un time.sleep() tra un'esecuzione e l'altra. In produzione, utilizzi un task scheduler come cron, APScheduler o una funzione cloud. Aggiunga sempre un controllo per evitare l'esecuzione nelle ore non lavorative, rispettando i fusi orari dei destinatari.
import time
import datetime
def is_business_hours():
now = datetime.datetime.now()
# Mon-Fri, 9am-6pm local time
return (now.weekday() < 5 and 9 <= now.hour < 18)
def run_scheduler(gmail_service, agent_context,
interval_minutes=30, max_emails=20):
print(f'Email agent started. Checking every {interval_minutes} min.')
while True:
if is_business_hours():
print(f'\n[{datetime.datetime.now().strftime("%H:%M")}] Running agent...')
try:
run_email_agent(gmail_service, agent_context, max_emails)
except Exception as e:
print(f'Agent run failed: {e}')
else:
print('Outside business hours, skipping run')
time.sleep(interval_minutes * 60)Registrazione e traccia di audit
Mantenga un log JSON di ogni email elaborata dall'agente. In questo modo crea una traccia di audit che può esaminare per verificare il comportamento dell'agente, diagnosticare classificazioni errate e migliorare i prompt nel tempo.
import json
import datetime
from pathlib import Path
LOG_FILE = Path('agent_audit.jsonl')
def log_action(email_id, subject, sender, category, action, draft_id=None):
entry = {
'timestamp': datetime.datetime.now().isoformat(),
'email_id': email_id,
'subject': subject[:100],
'from': sender,
'category': category,
'action': action,
'draft_id': draft_id
}
with open(LOG_FILE, 'a', encoding='utf-8') as f:
f.write(json.dumps(entry) + '\n')
# Usage in the main loop:
log_action(
email_id='18abc123',
subject='Invoice #1234',
sender='billing@vendor.com',
category='action_needed',
action='draft_created',
draft_id='r9000abc'
)
# --- demo ---
print('Audit log contents:')
print(LOG_FILE.read_text(encoding='utf-8'))
Verifica rapida: supervisione umana
Verifichi la Sua comprensione della progettazione dell'assistente email.
Riepilogo dell'agente assistente email
Ha creato una pipeline completa per un agente assistente email:
- Recupero: recuperare le email non lette con le query di ricerca dell'API Gmail
- Classificazione: l'LLM classifica ogni email come action_needed/fyi/spam con output JSON
- Bozza: l'LLM genera una risposta con tono professionale e segnaposto [FILL IN]
- Salvataggio della bozza: conservarla in Gmail per la revisione umana; non inviarla mai automaticamente
- Contrassegno come elaborata: aggiungere un'etichetta personalizzata e rimuovere UNREAD per impedire una nuova elaborazione
- Isolamento degli errori: inserire ogni email in un blocco try/except per impedire che un singolo errore interrompa il batch
- Log di audit: un file JSONL che registra ogni azione per la revisione e il miglioramento
Domande Frequenti
La lezione «Creazione di un semplice agente assistente email» è gratuita?
Sì — il testo completo di «Creazione di un semplice agente assistente email» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso AI Agents, passa a CoddyKit PRO. Il corso AI Agents include 4 lezioni in totale.
Cosa imparerò in «Creazione di un semplice agente assistente email»?
Dall’inizio alla fine: lettura della posta in arrivo → riepilogo → bozza della risposta → attesa dell’approvazione Eserciti AI Agents con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare AI Agents?
Non è richiesta alcuna esperienza precedente. AI Agents su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.
Quanto tempo richiede la lezione «Creazione di un semplice agente assistente email»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione AI Agents?
Sì. Ogni lezione AI Agents include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Connessione a Gmail tramite API
- Lettura e invio programmatico delle email
- Creazione e consultazione degli eventi del calendario
- Creazione di un semplice agente assistente email