Агент автоответа по почте

Обзор

Соберите агента автоответа: входящие письма обрабатываются автоматически. Пример для начинающих по Agent Inbox: вебхуки, события почты и отправка ответов.

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

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

  1. Принимает письма в выделенный inbox Agent Inbox
  2. Обрабатывает вебхуки в реальном времени
  3. Извлекает данные отправителя (имя и email)
  4. Формирует персонализированные ответы по шаблону
  5. Отправляет автоответы отправителю

Схема для пользователя:

User sends email → Agent Inbox inbox receives → Agent processes it → User gets reply
"Hi John, thank you for your email!
I've received your message and will
get back to you within 24 hours..."

Требования

Перед началом подготовьте:

Нужно:

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

Шаг 1: каталог проекта

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

$mkdir auto-reply-agent
$cd auto-reply-agent

Шаг 2: код агента

Создайте agent.py и вставьте код:

1"""
2Агент автоответа на почту
3
4Простой пример: бот автоответа на Agent Inbox.
5Агент автоматически отвечает на входящие письма персонализированными сообщениями.
6"""
7
8import os
9from dotenv import load_dotenv
10
11# Load environment variables before importing Agent Inbox
12load_dotenv()
13
14from flask import Flask, request, Response
15import ngrok
16from agentinbox import Agentinbox
17import threading
18
19# Configuration
20PORT = 8080
21INBOX_USERNAME = os.getenv("INBOX_USERNAME", "auto-reply")
22WEBHOOK_DOMAIN = os.getenv("WEBHOOK_DOMAIN")
23
24# Initialize Flask app and Agent Inbox client
25app = Flask(__name__)
26client = Agentinbox()
27processed_messages = set() # Track processed message IDs to prevent duplicates
28
29
30def setup_agentinbox():
31 """Create inbox and webhook with idempotency."""
32 print("Setting up Agent Inbox infrastructure...")
33
34 # Create inbox (or get existing one)
35 try:
36 inbox = client.inboxes.create(
37 username=INBOX_USERNAME,
38 client_id=f"{INBOX_USERNAME}-inbox"
39 )
40 print(f"✓ Inbox created: {inbox.inbox_id}")
41 except Exception as e:
42 if "already exists" in str(e).lower():
43 inbox_id = f"{INBOX_USERNAME}@agentinbox.space"
44 class SimpleInbox:
45 def __init__(self, inbox_id):
46 self.inbox_id = inbox_id
47 inbox = SimpleInbox(inbox_id)
48 print(f"✓ Using existing inbox: {inbox.inbox_id}")
49 else:
50 raise
51
52 # Start ngrok tunnel
53 listener = ngrok.forward(PORT, domain=WEBHOOK_DOMAIN, authtoken_from_env=True)
54
55 # Create webhook (or get existing one)
56 try:
57 webhook = client.webhooks.create(
58 url=f"{listener.url()}/webhook/inbound",
59 event_types=["message.received"],
60 inbox_ids=[inbox.inbox_id],
61 client_id=f"{INBOX_USERNAME}-webhook"
62 )
63 print(f"✓ Webhook created")
64 except Exception as e:
65 if "already exists" in str(e).lower():
66 print(f"Webhook already exists")
67 else:
68 raise
69
70 print(f"\n✓ Setup complete!")
71 print(f"Inbox: {inbox.inbox_id}")
72 print(f"Webhook: {listener.url()}/webhook/inbound\n")
73
74 return inbox, listener
75
76
77def generate_reply(sender_name, subject):
78 """Generate auto-reply message using a template."""
79 return (
80 f"Hi {sender_name},\n\n"
81 f"Thank you for your email! I've received your message and will get back to you within 24 hours.\n\n"
82 f"If your matter is urgent, please reply with \"URGENT\" in the subject line.\n\n"
83 f"Best regards,\n"
84 f"Auto-Reply Agent"
85 )
86
87
88def process_and_reply(message_id, inbox_id, from_field, subject, message):
89 """Process incoming message and send reply in background."""
90 # Extract sender email and name
91 if '<' in from_field and '>' in from_field:
92 sender_email = from_field.split('<')[1].split('>')[0].strip()
93 sender_name = from_field.split('<')[0].strip()
94 if not sender_name or ',' in sender_name:
95 sender_name = sender_email.split('@')[0].title()
96 else:
97 sender_email = from_field.strip()
98 sender_name = sender_email.split('@')[0].title() if '@' in sender_email else 'Friend'
99
100 # Log incoming email
101 print(f"Processing email from {sender_email}: {subject}")
102
103 # Generate and send auto-reply
104 try:
105 reply_text = generate_reply(sender_name, subject)
106 client.inboxes.messages.reply(
107 inbox_id=inbox_id,
108 message_id=message_id,
109 to=[sender_email],
110 text=reply_text
111 )
112 print(f"Auto-reply sent to {sender_email}\n")
113 except Exception as e:
114 print(f"Error: {e}\n")
115
116
117@app.route('/webhook/inbound', methods=['POST'])
118def receive_webhook():
119 """Webhook endpoint to receive incoming email notifications."""
120 payload = request.json
121 event_type = payload.get('type') or payload.get('event_type')
122
123 # Ignore outgoing messages
124 if event_type == 'message.sent':
125 return Response(status=200)
126
127 message = payload.get('message', {})
128 message_id = message.get('message_id')
129 inbox_id = message.get('inbox_id')
130 from_field = message.get('from_', '') or message.get('from', '')
131
132 # Validate required fields
133 if not message_id or not inbox_id or not from_field:
134 return Response(status=200)
135
136 # prevent duplicate
137 if message_id in processed_messages:
138 return Response(status=200)
139 processed_messages.add(message_id)
140
141 subject = message.get('subject', '(no subject)')
142
143 # Process in background thread and return immediately
144 thread = threading.Thread(
145 target=process_and_reply,
146 args=(message_id, inbox_id, from_field, subject, message)
147 )
148 thread.daemon = True
149 thread.start()
150
151 return Response(status=200)
152
153
154if __name__ == '__main__':
155 print("\n" + "="*60)
156 print("AUTO-REPLY EMAIL AGENT")
157 print("="*60 + "\n")
158
159 inbox, listener = setup_agentinbox()
160
161 print(f"Agent is ready!")
162 print(f"Send emails to: {inbox.inbox_id}")
163 print(f"\nWaiting for incoming emails...\n")
164
165 app.run(port=PORT)

Шаг 3: requirements.txt

Файл requirements.txt:

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

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

$pip install -r requirements.txt

Шаг 5: переменные окружения

Файл .env с учётными данными:

# Agent Inbox Configuration
AGENTINBOX_API_KEY=your_agentinbox_api_key_here
# Ngrok Configuration
NGROK_AUTHTOKEN=your_ngrok_authtoken_here
WEBHOOK_DOMAIN=your-name.ngrok-free.app
# Inbox Settings
INBOX_USERNAME=auto-reply

Разбор кода

Как устроен агент по шагам.

Схема

┌─────────────┐
│ Отправитель │
│ шлёт │ ──────► ┌──────────────┐
│ письмо │ │ Agent Inbox │
└─────────────┘ │ Inbox │
└──────┬───────┘
│ Webhook
┌──────────────┐
│ Ngrok │
│ Tunnel │
└──────┬───────┘
┌──────────────┐
│ Flask │
│ сервер │
└──────┬───────┘
┌──────────────┐
│ Генерация │
│ и отправка │
│ ответа │
└──────────────┘

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

1import os
2from dotenv import load_dotenv
3
4# Load environment variables FIRST
5load_dotenv()
6
7from flask import Flask, request, Response
8import ngrok
9from agentinbox import Agentinbox
10
11# Initialize the Agent Inbox client
12client = Agentinbox() # Reads AGENTINBOX_API_KEY from environment
13app = Flask(__name__)

Важно:

  • Сначала load_dotenv(), затем импорт Agent Inbox
  • SDK читает AGENTINBOX_API_KEY из окружения
  • Flask принимает вебхуки

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

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

1def setup_agentinbox():
2 """Create inbox and webhook with idempotency."""
3
4 # Create inbox (or get existing one)
5 try:
6 inbox = client.inboxes.create(
7 username=INBOX_USERNAME,
8 client_id=f"{INBOX_USERNAME}-inbox" # ← Idempotency key
9 )
10 print(f"✓ Inbox created: {inbox.inbox_id}")
11 except Exception as e:
12 if "already exists" in str(e).lower():
13 # Inbox already exists, that's fine!
14 inbox_id = f"{INBOX_USERNAME}@agentinbox.space"
15 inbox = SimpleInbox(inbox_id)

Зачем client_id?

С ним операция идемпотентна: повторные запуски не создают дубликаты — вернётся существующий inbox.

1 # Start ngrok tunnel
2 listener = ngrok.forward(PORT, domain=WEBHOOK_DOMAIN, authtoken_from_env=True)
3
4 # Create webhook
5 webhook = client.webhooks.create(
6 url=f"{listener.url()}/webhook/inbound",
7 event_types=["message.received"], # ← Only subscribe to incoming emails
8 client_id=f"{INBOX_USERNAME}-webhook" # ← Idempotency
9 )

Что происходит:

  1. Ngrok даёт публичный URL на localhost:8080
  2. Регистрируется вебхук в Agent Inbox
  3. При новых письмах Agent Inbox шлёт POST на этот URL

3. Обработка вебхуков

Эндпоинт принимает уведомления о входящей почте:

1@app.route('/webhook/inbound', methods=['POST'])
2def receive_webhook():
3 """Webhook endpoint to receive incoming email notifications."""
4 payload = request.json
5 event_type = payload.get('type') or payload.get('event_type')
6
7 # Ignore outgoing messages (prevents infinite loops!)
8 if event_type == 'message.sent':
9 return Response(status=200)

Почему игнорируем message.sent?

После ответа агента приходит вебхук message.sent. Без фильтра агент воспримет свой ответ как новое письмо и уйдёт в бесконечный цикл.

Ответ 200 значит: событие получено, обрабатывать его не нужно.

4. Данные из письма

1 message = payload.get('message', {})
2 message_id = message.get('message_id')
3 inbox_id = message.get('inbox_id')
4 from_field = message.get('from_', '') or message.get('from', '')
5
6 # Validate required fields
7 if not message_id or not inbox_id or not from_field:
8 return Response(status=200) # Gracefully skip incomplete data

Структура payload вебхука:

1{
2 "type": "message.received",
3 "message": {
4 "message_id": "<abc123@agentinbox.space>",
5 "inbox_id": "auto-reply@agentinbox.space",
6 "from_": "John Doe <john@example.com>",
7 "subject": "Hello",
8 "text": "Email body content..."
9 }
10}

5. Разбор адреса отправителя

Поддерживаются разные форматы From:

1# Extract sender email and name
2if '<' in from_field and '>' in from_field:
3 # Format: "John Doe <john@example.com>"
4 sender_email = from_field.split('<')[1].split('>')[0].strip()
5 sender_name = from_field.split('<')[0].strip()
6 if not sender_name or ',' in sender_name:
7 # Name is empty or has comma, use email username
8 sender_name = sender_email.split('@')[0].title()
9else:
10 # Format: "john@example.com"
11 sender_email = from_field.strip()
12 sender_name = sender_email.split('@')[0].title()

Примеры:

  • "John Doe <john@example.com>" → имя: “John Doe”, email: “john@example.com
  • "john@example.com" → имя: “John”, email: “john@example.com
  • "Last, First <name@example.com>" → имя: “Name”, email: “name@example.com

6. Генерация ответа

1def generate_reply(sender_name, subject):
2 """Generate auto-reply message using a template."""
3 return (
4 f"Hi {sender_name},\n\n"
5 f"Thank you for your email! I've received your message and will get back to you within 24 hours.\n\n"
6 f"If your matter is urgent, please reply with \"URGENT\" in the subject line.\n\n"
7 f"Best regards,\n"
8 f"Auto-Reply Agent"
9 )

Шаблон без AI и внешних API; подставляется имя отправителя.

7. Отправка ответа

1try:
2 reply_text = generate_reply(sender_name, subject)
3 client.inboxes.messages.reply(
4 inbox_id=inbox_id,
5 message_id=message_id,
6 to=[sender_email], # ← Must be a list!
7 text=reply_text
8 )
9 print(f"Auto-reply sent to {sender_email}\n")
10except Exception as e:
11 print(f"Error: {e}\n")
12
13return Response(status=200) # Always return 200 to acknowledge webhook

Детали:

  • toсписок адресов
  • message_id связывает ответ с исходным письмом (тред)
  • На вебхук всегда отвечайте 200
  • Ошибки логируются, сервер не падает

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

Даже если отправка ответа не удалась, 200 сообщает Agent Inbox, что вебхук принят. Иначе платформа будет ретраить доставку — это не лечит ошибки приложения.

Запуск агента

$python agent.py

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

============================================================
AUTO-REPLY EMAIL AGENT
============================================================
Setting up Agent Inbox infrastructure...
✓ Inbox created: auto-reply@agentinbox.space
✓ Webhook created
✓ Setup complete!
Inbox: auto-reply@agentinbox.space
Webhook: https://your-name.ngrok-free.app/webhook/inbound
Agent is ready!
Send emails to: auto-reply@agentinbox.space
Reply mode: Template-based
Waiting for incoming emails...
* Running on http://127.0.0.1:8080

Готово. Агент запущен и ждёт письма.

Не закрывайте терминал — иначе агент остановится.

Проверка

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

Тестовое письмо

  1. Откройте личную почту (Gmail, Outlook и т.д.)
  2. Новое сообщение:
    To: auto-reply@agentinbox.space
    Subject: Testing my auto-reply agent
    Body: Hi there! This is a test message.
  3. Отправьте письмо

В терминале

Email from youremail@gmail.com: Testing my auto-reply agent
Auto-reply sent to youremail@gmail.com

Входящие

Через несколько секунд придёт автоответ:

Hi Youremail,
Thank you for your email! I've received your message and will get back to you within 24 hours.
If your matter is urgent, please reply with "URGENT" in the subject line.
Best regards,
Auto-Reply Agent

Работает. Вы собрали и проверили первого агента Agent Inbox: имя извлечено, ответ персонализирован.

Настройка под себя

Измените текст автоответа в generate_reply() в agent.py.

Доступны:

  • sender_name — имя из From
  • subject — тема письма

Правьте строки в return, чтобы менять ответ.

Устранение неполадок

Частые проблемы

Проблема: не установлены зависимости.

Решение:

$pip install -r requirements.txt

Активируйте venv, если используете:

$source venv/bin/activate # macOS/Linux
$venv\Scripts\activate # Windows

Проблема: неверный или отсутствующий API-ключ.

Что сделать:

  1. В .env корректный AGENTINBOX_API_KEY
  2. Ключ действителен в панели Agent Inbox
  3. Нет лишних пробелов или кавычек
  4. .env лежит рядом с agent.py

Проверка ключа:

1from agentinbox import Agentinbox
2client = Agentinbox()
3print(client.inboxes.list()) # Should succeed

Проблема: неверный или отсутствующий токен ngrok.

Что сделать:

  1. Токен в ngrok dashboard
  2. Обновите NGROK_AUTHTOKEN в .env
  3. Без лишних пробелов

Или глобально:

$ngrok config add-authtoken YOUR_TOKEN

Чеклист:

  • Агент запущен? (python agent.py — «Waiting for incoming emails…»)
  • Туннель ngrok активен? (URL вебхука в консоли)
  • Письмо на правильный inbox?
  • URL вебхука доступен? curl https://your-domain.ngrok-free.app/webhook/inbound

Отладка:

  1. Логируйте payload:
1@app.route('/webhook/inbound', methods=['POST'])
2def receive_webhook():
3 payload = request.json
4 print(f"📨 Received webhook: {json.dumps(payload, indent=2)}")
5 # ... rest of code ...
  1. Панель ngrok — логи запросов

  2. Вебхук зарегистрирован:

1client = Agentinbox()
2webhooks = client.webhooks.list()
3print(webhooks)

Проблема: порт 8080 занят.

Вариант 1: завершите процесс на порту

$# macOS/Linux
$lsof -ti:8080 | xargs kill -9
$
$# Windows
$netstat -ano | findstr :8080
$taskkill /PID <PID> /F

Вариант 2: другой порт

1PORT = 8081 # Change in agent.py

Проблема: вебхук есть, ответа нет.

Шаги:

  1. Ошибки в консоли
  2. Параметры reply API:
1print(f"Sending to: {sender_email}")
2print(f"From inbox: {inbox_id}")
3print(f"Reply text: {reply_text[:50]}...")
  1. Прямой вызов reply API:
1client.inboxes.messages.reply(
2 inbox_id="your-inbox@agentinbox.space",
3 message_id="test-message-id",
4 to=["test@example.com"],
5 text="Test reply"
6)
  1. to должен быть списком:
1# Wrong
2to=sender_email
3
4# Correct
5to=[sender_email]

Поздравляем — первый агент Agent Inbox готов.

Дополнительно: ответы с ИИ (OpenAI)

Можно добавить осмысленные ответы через OpenAI.

Шаг 1: установка OpenAI

$pip install openai

Шаг 2: ключ OpenAI в .env

# AI Configuration
USE_AI_REPLY=true
OPENAI_API_KEY=sk-your_actual_openai_api_key_here

Шаг 3: функции треда и ИИ в agent.py

После generate_reply() добавьте:

1def get_thread_history(thread_id):
2 """Fetch conversation history for the thread."""
3 try:
4 thread = client.threads.get(thread_id=thread_id)
5 return thread.messages if hasattr(thread, 'messages') else []
6 except Exception as e:
7 print(f"Failed to fetch thread history: {e}")
8 return []
9
10
11def format_thread_for_ai(messages):
12 """Format thread messages into conversation history for AI."""
13 conversation = []
14
15 for msg in messages:
16 if hasattr(msg, 'from_'):
17 sender = msg.from_
18 text = msg.text or msg.html or ""
19 else:
20 sender = msg.get('from_', '') or msg.get('from', '')
21 text = msg.get('text', '') or msg.get('html', '') or msg.get('body', '')
22
23 if '<' in sender and '>' in sender:
24 sender = sender.split('<')[1].split('>')[0].strip()
25
26 if text:
27 conversation.append(f"From: {sender}\n{text}")
28
29 return "\n\n---\n\n".join(reversed(conversation))
30
31
32def generate_ai_reply(sender_name, email_body, subject, thread_history=""):
33 """Generate AI-powered reply using OpenAI with thread context."""
34 try:
35 context = f"Email thread history:\n\n{thread_history}\n\n---\n\nLatest message from {sender_name}:\nSubject: {subject}\n{email_body}" if thread_history else f"Subject: {subject}\nFrom: {sender_name}\n{email_body}"
36
37 response = openai_client.chat.completions.create(
38 model="gpt-4o-mini",
39 messages=[
40 {
41 "role": "system",
42 "content": "You are an intelligent email assistant. Read the email thread and respond in a helpful, contextual way. If this is a follow-up in a conversation, acknowledge what was previously discussed. If you can provide helpful information based on the context, do so. If the question requires detailed research or expertise you don't have, acknowledge receipt and set expectations. Be conversational, professional, and concise."
43 },
44 {
45 "role": "user",
46 "content": f"{context}\n\nGenerate a helpful reply that considers the conversation history. Keep it concise (2-4 sentences) but be actually helpful if you can address their question or continue the conversation meaningfully."
47 }
48 ],
49 max_tokens=250,
50 temperature=0.7
51 )
52 return response.choices[0].message.content
53 except Exception as e:
54 print(f"AI generation failed, using template: {e}")
55 return generate_reply(sender_name, subject)

Шаг 4: обновите обработчик вебхука

В receive_webhook() добавьте thread_id и ветку с ИИ:

1 subject = message.get('subject', '(no subject)')
2 thread_id = message.get('thread_id', '') # Add this line
3
4 # Log incoming email
5 print(f"Email from {sender_email}: {subject}")
6
7 # Generate and send auto-reply
8 try:
9 if USE_AI_REPLY:
10 email_body = message.get('text', '') or message.get('body', '')
11
12 # Fetch thread history for context
13 thread_history = ""
14 if thread_id:
15 print(f"Fetching thread history for: {thread_id[:20]}...")
16 messages = get_thread_history(thread_id)
17 if messages:
18 thread_history = format_thread_for_ai(messages)
19 print(f"Found {len(messages)} messages in thread")
20
21 reply_text = generate_ai_reply(sender_name, email_body, subject, thread_history)
22 print("Using AI-generated reply with thread context")
23 else:
24 reply_text = generate_reply(sender_name, subject)
25 print("Using template reply")
26
27 client.inboxes.messages.reply(
28 inbox_id=inbox_id,
29 message_id=message_id,
30 to=[sender_email],
31 text=reply_text
32 )
33 print(f"Auto-reply sent to {sender_email}\n")
34 except Exception as e:
35 print(f"Error: {e}\n")

Как это работает:

  • История треда через client.threads.get(thread_id)
  • История форматируется и уходит в OpenAI
  • Ответы с учётом контекста
  • При ошибке OpenAI в примере ниже можно добавить повторы (в тексте выше была неточность про «three times» — реализуйте ретраи при необходимости)

Итог: у агента появляется память переписки; на фоллоу-апы отвечает с контекстом, а не шаблоном.


Сделали что-то интересное на Agent Inbox? Напишите нам: info@patternautomation.com.