Агент умной разметки писем

Обзор

Соберите агента классификации почты: ИИ анализирует письма и проставляет метки. Средний уровень: метки Agent Inbox и OpenAI GPT-4o-mini для автоматизации inbox.

Что вы получите

В конце руководства будет агент, который:

  1. Принимает письма в выделенный inbox Agent Inbox
  2. Анализирует каждое письмо ИИ по 4 измерениям:
    • Sentiment (тональность): positive, neutral, negative
    • Category (категория): question, complaint, feature-request, bug-report, praise
    • Priority (приоритет): urgent, high, normal, low
    • Department (отдел): sales, support, billing, technical
  3. Автоматически ставит метки для фильтрации
  4. Обрабатывает сбои — повторы и валидация ответа модели

Пример прихода письма:

Письмо: "Your product crashed! I need help ASAP!"
Анализ ИИ
Метки:
• negative
• complaint
• urgent
• support

Требования

Нужно:

Настройка проекта

Шаг 1: каталог

Создайте папку агента:

$mkdir smart-labeling-agent
$cd smart-labeling-agent

Шаг 2: код agent.py

1"""
2Smart Email Labeling Agent
3
4An AI-powered email classification agent that automatically analyzes incoming
5emails across multiple dimensions and applies appropriate labels.
6"""
7
8import os
9import json
10import time
11from dotenv import load_dotenv
12
13load_dotenv()
14
15from flask import Flask, request, Response
16import ngrok
17from agentinbox import Agentinbox
18from openai import OpenAI
19
20# Configuration
21PORT = int(os.getenv("PORT", "8080"))
22INBOX_USERNAME = os.getenv("INBOX_USERNAME", "smart-labels")
23WEBHOOK_DOMAIN = os.getenv("WEBHOOK_DOMAIN")
24
25# Initialize
26app = Flask(__name__)
27client = Agentinbox()
28openai_client = OpenAI()
29
30
31def setup_agentinbox():
32 """Create inbox and webhook with idempotency."""
33 # Create inbox
34 try:
35 inbox = client.inboxes.create(
36 username=INBOX_USERNAME,
37 client_id=f"{INBOX_USERNAME}-inbox"
38 )
39 except Exception as e:
40 if "already exists" in str(e).lower():
41 inbox_id = f"{INBOX_USERNAME}@agentinbox.space"
42 class SimpleInbox:
43 def __init__(self, inbox_id):
44 self.inbox_id = inbox_id
45 inbox = SimpleInbox(inbox_id)
46 else:
47 raise
48
49 # Start ngrok
50 listener = ngrok.forward(PORT, domain=WEBHOOK_DOMAIN, authtoken_from_env=True)
51
52 # Create webhook
53 try:
54 client.webhooks.create(
55 url=f"{listener.url()}/webhook/inbound",
56 event_types=["message.received"],
57 client_id=f"{INBOX_USERNAME}-webhook"
58 )
59 except Exception as e:
60 if "already exists" not in str(e).lower():
61 raise
62
63 print(f"Ready: {inbox.inbox_id}\n")
64 return inbox, listener
65
66
67def analyze_email(subject, content):
68 """Use AI to classify email across multiple dimensions with retry logic."""
69 valid_values = {
70 "sentiment": {"positive", "neutral", "negative"},
71 "category": {"question", "complaint", "feature-request", "bug-report", "praise"},
72 "priority": {"urgent", "high", "normal", "low"},
73 "department": {"sales", "support", "billing", "technical"}
74 }
75
76 for attempt in range(1, 4):
77 try:
78 if attempt > 1:
79 time.sleep(1)
80
81 response = openai_client.chat.completions.create(
82 model="gpt-4o-mini",
83 messages=[
84 {
85 "role": "system",
86 "content": "You are an expert email classifier. Analyze emails and return structured classifications."
87 },
88 {
89 "role": "user",
90 "content": f"""Analyze this email across 4 dimensions:
91
92 Subject: {subject}
93 Content: {content}
94
95 Classify into:
96 1. sentiment: positive | neutral | negative
97 2. category: question | complaint | feature-request | bug-report | praise
98 3. priority: urgent | high | normal | low
99 4. department: sales | support | billing | technical
100
101 Consider:
102 - Sentiment: Overall tone and emotion
103 - Category: Primary intent of the email
104 - Priority: Urgency indicators (ASAP, urgent, immediately, deadline mentions, emergency)
105 - Department: Best team to handle this
106
107 Return ONLY valid JSON with these exact keys: sentiment, category, priority, department.
108 Example: {{"sentiment": "positive", "category": "question", "priority": "normal", "department": "sales"}}
109 """
110 }
111 ],
112 response_format={"type": "json_object"},
113 temperature=0.3
114 )
115
116 # Parse and validate
117 result = json.loads(response.choices[0].message.content)
118
119 required_keys = ["sentiment", "category", "priority", "department"]
120 missing_keys = [key for key in required_keys if key not in result]
121 if missing_keys:
122 raise ValueError(f"Missing keys: {missing_keys}")
123
124 invalid_values = []
125 for dimension, value in result.items():
126 if dimension in valid_values and value not in valid_values[dimension]:
127 invalid_values.append(f"{dimension}={value}")
128
129 if invalid_values:
130 raise ValueError(f"Invalid values: {', '.join(invalid_values)}")
131
132 return result
133
134 except Exception as e:
135 if attempt == 3:
136 raise Exception(f"AI classification failed: {e}")
137
138
139def apply_labels(inbox_id, message_id, classifications):
140 """Apply labels based on classification results."""
141 labels = [
142 f"{classifications['sentiment']}",
143 f"{classifications['category']}",
144 f"{classifications['priority']}",
145 f"{classifications['department']}"
146 ]
147
148 # Try batch first
149 try:
150 client.inboxes.messages.update(
151 inbox_id=inbox_id,
152 message_id=message_id,
153 add_labels=labels
154 )
155 for label in labels:
156 print(f" ✓ {label}")
157 return
158 except Exception:
159 pass
160
161 # Try individually
162 successful = []
163 for label in labels:
164 try:
165 client.inboxes.messages.update(
166 inbox_id=inbox_id,
167 message_id=message_id,
168 add_labels=[label]
169 )
170 successful.append(label)
171 print(f" ✓ {label}")
172 except Exception:
173 print(f" ✗ {label}")
174
175 if not successful:
176 raise Exception("Failed to apply labels")
177
178
179@app.route('/webhook/inbound', methods=['POST'])
180def receive_webhook():
181 """Webhook endpoint to receive incoming email notifications."""
182 try:
183 payload = request.json
184 event_type = payload.get('type') or payload.get('event_type')
185
186 # Ignore outgoing messages
187 if event_type == 'message.sent':
188 return Response(status=200)
189
190 message = payload.get('message', {})
191 message_id = message.get('message_id')
192 inbox_id = message.get('inbox_id')
193 from_field = message.get('from_', '') or message.get('from', '')
194
195 # Validate required fields
196 if not message_id or not inbox_id or not from_field:
197 return Response(status=200)
198
199 # Extract sender email
200 if '<' in from_field and '>' in from_field:
201 sender_email = from_field.split('<')[1].split('>')[0].strip()
202 else:
203 sender_email = from_field.strip()
204
205 subject = message.get('subject', '(no subject)')
206 email_body = message.get('text', '') or message.get('body', '') or message.get('html', '')
207
208 # Log
209 print(f"\n📧 {sender_email}: {subject}")
210
211 # Analyze
212 classifications = analyze_email(subject, email_body)
213
214 print(f" Sentiment: {classifications['sentiment']}")
215 print(f" Category: {classifications['category']}")
216 print(f" Priority: {classifications['priority']}")
217 print(f" Department: {classifications['department']}")
218
219 # Apply labels
220 apply_labels(inbox_id, message_id, classifications)
221 print("Done\n")
222
223 except Exception as e:
224 print(f"Error: {e}\n")
225
226 return Response(status=200)
227
228
229if __name__ == '__main__':
230 print("SMART EMAIL LABELING AGENT\n")
231 inbox, listener = setup_agentinbox()
232 print("Waiting for emails...\n")
233 app.run(port=PORT)

Шаг 3: requirements.txt

agentinbox
flask>=3.0.0
ngrok>=1.0.0
python-dotenv>=1.0.0
openai>=1.0.0

Шаг 4: установка зависимостей

$pip install -r requirements.txt

Шаг 5: .env

# Agent Inbox Configuration
AGENTINBOX_API_KEY=your_agentinbox_api_key_here
# OpenAI Configuration
OPENAI_API_KEY=your_openai_api_key_here
# Ngrok Configuration
NGROK_AUTHTOKEN=your_ngrok_authtoken_here
WEBHOOK_DOMAIN=your-domain.ngrok-free.app
# Agent Settings
INBOX_USERNAME=smart-labels
PORT=8080

Разбор кода

Схема

Письмо → Agent Inbox → Webhook → ngrok → Flask → ИИ → Метки

1. Инициализация

1# Load environment variables first
2load_dotenv()
3
4# Initialize three clients
5app = Flask(__name__) # Web server for webhooks
6client = Agentinbox() # Agent Inbox SDK
7openai_client = OpenAI() # OpenAI for AI classification

2. Инфраструктура

setup_agentinbox() создаёт inbox и вебхук:

1# Create inbox with idempotency
2inbox = client.inboxes.create(
3 username=INBOX_USERNAME,
4 client_id=f"{INBOX_USERNAME}-inbox" # Prevents duplicates
5)
6
7# Start ngrok tunnel (localhost → public URL)
8listener = ngrok.forward(PORT, domain=WEBHOOK_DOMAIN)
9
10# Register webhook with Agent Inbox
11client.webhooks.create(
12 url=f"{listener.url()}/webhook/inbound",
13 event_types=["message.received"],
14 client_id=f"{INBOX_USERNAME}-webhook"
15)

Идемпотентность client_id: перезапуск агента не создаёт дубликаты — вернётся существующий ресурс.

3. Анализ писем ИИ

Ядро агента — analyze_email():

1def analyze_email(subject, content):
2 # Define valid classification values
3 valid_values = {
4 "sentiment": {"positive", "neutral", "negative"},
5 "category": {"question", "complaint", "feature-request", ...},
6 "priority": {"urgent", "high", "normal", "low"},
7 "department": {"sales", "support", "billing", "technical"}
8 }
9
10 # Retry up to 3 times
11 for attempt in range(1, 4):
12 try:
13 # Call OpenAI API
14 response = openai_client.chat.completions.create(...)
15
16 # Parse JSON response
17 result = json.loads(response.choices[0].message.content)
18
19 # Validate keys and values
20 # ... validation logic ...
21
22 return result
23 except Exception as e:
24 if attempt == 3:
25 raise

Особенности:

  1. Структурированный промпт — четыре измерения классификации
  2. JSON mode — валидный JSON от OpenAI
  3. Повторы — до 3 попыток при ошибках
  4. Строгая валидация — значения только из допустимых множеств
  5. Низкая temperature (0.3) — стабильные метки

Пример промпта:

Проанализируйте это письмо по 4 измерениям:
Тема: Продукт постоянно падает!
Текст: Ваш софт ужасен. Исправьте немедленно!
Классифицируйте:
1. sentiment: positive | neutral | negative
2. category: question | complaint | feature-request | bug-report | praise
3. priority: urgent | high | normal | low
4. department: sales | support | billing | technical
Верните ТОЛЬКО валидный JSON...

Ответ модели:

1{
2 "sentiment": "negative",
3 "category": "complaint",
4 "priority": "urgent",
5 "department": "support"
6}

4. Простановка меток

apply_labels() — двухуровневая стратегия:

1# Create labels from classification values
2labels = [
3 "negative",
4 "complaint",
5 "urgent",
6 "support"
7]
8
9# Try batch application first (most efficient)
10try:
11 client.inboxes.messages.update(
12 inbox_id=inbox_id,
13 message_id=message_id,
14 add_labels=labels # Apply all at once
15 )
16 return
17except Exception:
18 pass # If batch fails, try individually
19
20# Apply labels one by one
21for label in labels:
22 try:
23 client.inboxes.messages.update(
24 inbox_id=inbox_id,
25 message_id=message_id,
26 add_labels=[label] # One at a time
27 )
28 except Exception:
29 pass # Log failure but continue

Зачем два уровня:

  1. Сначала пакет — один вызов API на все метки
  2. Потом по одной — если пакет не прошёл, сохраняем хотя бы часть
  3. Устойчивость — сбой одной метки не роняет всё

5. Вебхук

receive_webhook() связывает шаги:

1@app.route('/webhook/inbound', methods=['POST'])
2def receive_webhook():
3 payload = request.json
4
5 # Ignore outgoing messages (prevents infinite loops)
6 if event_type == 'message.sent':
7 return Response(status=200)
8
9 # Extract email data
10 message_id = message.get('message_id')
11 inbox_id = message.get('inbox_id')
12 sender_email = extract_email(from_field)
13 subject = message.get('subject')
14 email_body = message.get('text')
15
16 # Classify with AI
17 classifications = analyze_email(subject, email_body)
18
19 # Apply labels
20 apply_labels(inbox_id, message_id, classifications)
21
22 # Always return 200 (tells Agent Inbox we received it)
23 return Response(status=200)

Почему всегда 200?

Даже при ошибке классификации или меток отвечаем 200 — вебхук принят. Коды 4xx/5xx вызовут ретраи доставки, что не лечит ошибки приложения.

Запуск

$python agent.py

Ожидаемый вывод:

SMART EMAIL LABELING AGENT
Ready: smart-labels@agentinbox.space
Waiting for emails...
* Running on http://127.0.0.1:8080

Готово. Агент классифицирует входящие письма. Терминал не закрывайте.

Проверка

Пример 1: срочная жалоба

Отправьте письмо:

To: smart-labels@agentinbox.space
Subject: Product crashed - need immediate help!
Body: Your product is TERRIBLE! It crashed 3 times today and I lost all my work.
I need this fixed IMMEDIATELY or I want a full refund!

Консоль:

you@example.com: Product crashed - need immediate help!
Sentiment: negative
Category: complaint
Priority: urgent
Department: support
✓ negative
✓ complaint
✓ urgent
✓ support
Done

Почему так:

  • Sentiment: negative («terrible», «lost work»)
  • Category: complaint
  • Priority: urgent («IMMEDIATELY», «crashed 3 times»)
  • Department: support (проблема продукта)

Пример 2: запрос фичи

Письмо:

To: smart-labels@agentinbox.space
Subject: Dark mode would be amazing!
Body: Hi! I absolutely love your product. I use it every day.
One feature that would make it even better is dark mode support.
Keep up the great work!

Консоль:

you@example.com: Dark mode would be amazing!
Sentiment: positive
Category: feature-request
Priority: normal
Department: technical
✓ positive
✓ feature-request
✓ normal
✓ technical
Done

Почему так:

  • Sentiment: positive («love», «amazing», «great work»)
  • Category: feature-request
  • Priority: normal
  • Department: technical

Дальше

В консоли и inbox

Фильтруйте письма по меткам в Agent Inbox:

Test image

По тональности: negative / positive / neutral и т.д.

Комбинации: urgent + negative — критичные кейсы; sales + high — горячие лиды; technical + bug-report — бэклог инженеров.

Совет: через API Agent Inbox можно выбирать письма по меткам и строить свои процессы, дашборды и аналитику.

Автоматизация на основе меток

1. Уведомления о приоритете

При метках urgent + negative — сразу Slack в канал саппорта с email отправителя и темой.

2. Эскалация по тональности

Собирайте negative, уведомляйте customer success при ухудшении тренда; при 3+ негативных письмах от одного клиента за неделю — персональный контакт от руководства.

3. Маршрутизация по отделу

Правила по меткам отдела: sales → sales@…; billing → тикет в биллинге; technical → #engineering; support → очередь с SLA.

4. Умный автоответ

Ответы по классу: question, bug-report, complaint, feature-request — разные шаблоны вместо одного общего.

5. Аналитика

Дашборд по меткам: тренды тональности, объём по отделам, время ответа по приоритетам, частые категории запросов.


Вы собрали систему классификации почты на ИИ; метки Agent Inbox позволяют строить сложную автоматизацию inbox и аналитику.