Узнайте, как создать агента автоответчика на электронные письма, который автоматически отвечает на входящие письма. Этот удобный для начинающих пример демонстрирует основные концепции создания с AgentInbox: получение веб-хуков, обработка событий электронной почты и отправка автоматических ответов.
К концу этого руководства у вас будет работающий агент автоответчика, который:
Получает входящие письма на выделенный входящий ящик AgentInbox
Обрабатывает события веб-хуков в реальном времени
Извлекает информацию об отправителе (имя и адрес электронной почты)
Генерирует персонализированные ответы с помощью шаблона
Отправляет автоматические ответы обратно отправителю
Вот как выглядит пользовательский опыт:
Copy
Пользователь отправляет письмо → AgentInbox получает → агент обрабатывает → пользователь получает ответ ↓ "Привет Андрей, спасибо за письмо! Я получил сообщение и отвечу тебе в течение 24 часов..."
Создайте файл с именем agent.py и вставьте следующий код:
Нажмите, чтобы просмотреть полный код agent.py
Copy
""" Агент автоответчик на электронные письма Простой пример, показывающий, как создать бота автоответчика на электронные письма с AgentInbox. Этот агент автоматически отвечает на входящие письма с персонализированными сообщениями. """ import os from dotenv import load_dotenv # Загрузите переменные окружения перед импортом AgentInbox load_dotenv() from flask import Flask, request, Response import ngrok from agentmail import AgentInbox import threading # Конфигурация PORT = 8080 INBOX_USERNAME = os.getenv("INBOX_USERNAME", "auto-reply") WEBHOOK_DOMAIN = os.getenv("WEBHOOK_DOMAIN") # Инициализируйте Flask приложение и клиент AgentInbox app = Flask(__name__) client = AgentMail() processed_messages = set() # Отслеживайте обработанные ID сообщений для предотвращения дубликатов def setup_agentmail(): """Создайте входящий ящик и веб-хук с идемпотентностью.""" print("Настройка инфраструктуры AgentInbox...") # Создайте входящий ящик (или получите существующий) try: inbox = client.inboxes.create( username=INBOX_USERNAME, client_id=f"{INBOX_USERNAME}-inbox" ) print(f"✓ Входящий ящик создан: {inbox.inbox_id}") except Exception as e: if "already exists" in str(e).lower(): inbox_id = f"{INBOX_USERNAME}@agentmail.to" class SimpleInbox: def __init__(self, inbox_id): self.inbox_id = inbox_id inbox = SimpleInbox(inbox_id) print(f"✓ Используется существующий входящий ящик: {inbox.inbox_id}") else: raise # Запустите туннель ngrok listener = ngrok.forward(PORT, domain=WEBHOOK_DOMAIN, authtoken_from_env=True) # Создайте веб-хук (или получите существующий) try: webhook = client.webhooks.create( url=f"{listener.url()}/webhook/agentmail", event_types=["message.received"], inbox_ids=[inbox.inbox_id], client_id=f"{INBOX_USERNAME}-webhook" ) print(f"✓ Веб-хук создан") except Exception as e: if "already exists" in str(e).lower(): print(f"Веб-хук уже существует") else: raise print(f"\n✓ Настройка завершена!") print(f"Входящий ящик: {inbox.inbox_id}") print(f"Веб-хук: {listener.url()}/webhook/agentmail\n") return inbox, listener def generate_reply(sender_name, subject): """Создайте сообщение автоответчика, используя шаблон.""" return ( f"Привет {sender_name},\n\n" f"Спасибо за ваше письмо! Я получил ваше сообщение и отвечу вам в течение 24 часов.\n\n" f"Если ваше дело срочно, пожалуйста, ответьте с \"СРОЧНО\" в строке темы.\n\n" f"С уважением,\n" f"Agent автоответчика" ) def process_and_reply(message_id, inbox_id, from_field, subject, message): """Обработайте входящее сообщение и отправьте ответ в фоновом режиме.""" # Извлеките адрес электронной почты и имя отправителя if '<' in from_field and '>' in from_field: sender_email = from_field.split('<')[1].split('>')[0].strip() sender_name = from_field.split('<')[0].strip() if not sender_name or ',' in sender_name: sender_name = sender_email.split('@')[0].title() else: sender_email = from_field.strip() sender_name = sender_email.split('@')[0].title() if '@' in sender_email else 'Friend' # Залогируйте входящее письмо print(f"Обработка письма от {sender_email}: {subject}") # Создайте и отправьте автоответ try: reply_text = generate_reply(sender_name, subject) client.inboxes.messages.reply( inbox_id=inbox_id, message_id=message_id, to=[sender_email], text=reply_text ) print(f"Автоответ отправлен на {sender_email}\n") except Exception as e: print(f"Ошибка: {e}\n") @app.route('/webhook/agentinbox', methods=['POST']) def receive_webhook(): """Конечная точка веб-хука для получения уведомлений о входящих письмах.""" payload = request.json event_type = payload.get('type') or payload.get('event_type') # Игнорируйте исходящие сообщения if event_type == 'message.sent': return Response(status=200) message = payload.get('message', {}) message_id = message.get('message_id') inbox_id = message.get('inbox_id') from_field = message.get('from_', '') or message.get('from', '') # Проверьте обязательные поля if not message_id or not inbox_id or not from_field: return Response(status=200) # Предотвратите дубликаты if message_id in processed_messages: return Response(status=200) processed_messages.add(message_id) subject = message.get('subject', '(нет темы)') # Обработайте в фоновом потоке и верните немедленно thread = threading.Thread( target=process_and_reply, args=(message_id, inbox_id, from_field, subject, message) ) thread.daemon = True thread.start() return Response(status=200) if __name__ == '__main__': print("\n" + "="*60) print("AGENT АВТООТВЕТЧИКА НА ЭЛЕКТРОННЫЕ ПИСЬМА") print("="*60 + "\n") inbox, listener = setup_agentmail() print(f"Агент готов!") print(f"Отправляйте письма на: {inbox.inbox_id}") print(f"\nОжидание входящих писем...\n") app.run(port=PORT)
Функция setup_agentmail() создает ваш входящий ящик и веб-хук:
Copy
def setup_agentinbox(): """Создайте входящий ящик и веб-хук с идемпотентностью.""" # Создайте входящий ящик (или получите существующий) try: inbox = client.inboxes.create( username=INBOX_USERNAME, client_id=f"{INBOX_USERNAME}-inbox" # ← Ключ идемпотентности ) print(f"✓ Входящий ящик создан: {inbox.inbox_id}") except Exception as e: if "already exists" in str(e).lower(): # Входящий ящик уже существует, это нормально! inbox_id = f"{INBOX_USERNAME}@agentmail.to" inbox = SimpleInbox(inbox_id)
Зачем client_id?Параметр client_id делает эту операцию идемпотентной — вы можете запустить ее несколько раз без создания дубликатов. Если входящий ящик уже существует, AgentInbox возвращает существующий.
Copy
# Запустите туннель ngrok listener = ngrok.forward(PORT, domain=WEBHOOK_DOMAIN, authtoken_from_env=True) # Создайте веб-хук webhook = client.webhooks.create( url=f"{listener.url()}/webhook/agentmail", event_types=["message.received"], # ← Подпишитесь только на входящие письма client_id=f"{INBOX_USERNAME}-webhook" # ← Идемпотентность )
Что происходит:
Ngrok создает открытый URL, который перенаправляет на localhost:8080
Мы регистрируем веб-хук с AgentInbox
AgentInbox будет постить на этот URL, когда прибудут письма
Конечная точка веб-хука получает уведомления о входящих письмах:
Copy
@app.route('/webhook/agentinbox', methods=['POST'])def receive_webhook(): """Конечная точка веб-хука для получения уведомлений о входящих письмах.""" payload = request.json event_type = payload.get('type') or payload.get('event_type') # Игнорируйте исходящие сообщения (предотвращает бесконечные циклы!) if event_type == 'message.sent': return Response(status=200)
Зачем игнорировать message.sent?Когда ваш агент отправляет ответ, AgentInbox запускает веб-хук message.sent. Если мы не отфильтруем это, агент будет рассматривать свои собственные ответы как новые письма и отвечать на себя бесконечно!Возвращая 200, мы говорим AgentInbox “я получил этот веб-хук успешно, но выбираю его не обрабатывать.”
message = payload.get('message', {}) message_id = message.get('message_id') inbox_id = message.get('inbox_id') from_field = message.get('from_', '') or message.get('from', '') # Проверьте обязательные поля if not message_id or not inbox_id or not from_field: return Response(status=200) # Gracefully пропустите неполные данные
Структура полезной нагрузки веб-хука:
Copy
{ "type": "message.received", "message": { "message_id": "abc123...", "inbox_id": "auto-reply@agentinbox.ru", "from_": "John Doe <john@example.com>", "subject": "Hello", "text": "Email body content..." }}
Адреса электронной почты могут приходить в разных форматах. Мы обрабатываем оба:
Copy
# Извлеките адрес электронной почты и имя отправителяif '<' in from_field and '>' in from_field: # Формат: "John Doe <john@example.com>" sender_email = from_field.split('<')[1].split('>')[0].strip() sender_name = from_field.split('<')[0].strip() if not sender_name or ',' in sender_name: # Имя пусто или содержит запятую, используйте имя пользователя электронной почты sender_name = sender_email.split('@')[0].title()else: # Формат: "john@example.com" sender_email = from_field.strip() sender_name = sender_email.split('@')[0].title()
Примеры:
"John Doe <john@example.com>" → имя: “John Doe”, адрес: “john@example.com”
def generate_reply(sender_name, subject): """Создайте сообщение автоответчика, используя шаблон.""" return ( f"Привет {sender_name},\n\n" f"Спасибо за ваше письмо! Я получил ваше сообщение и отвечу вам в течение 24 часов.\n\n" f"Если ваше дело срочно, пожалуйста, ответьте с \"СРОЧНО\" в строке темы.\n\n" f"С уважением,\n" f"Агент автоответчик" )
Этот простой подход на основе шаблонов не требует ИИ или внешних API. Ответ персонализирован с именем отправителя.
try: reply_text = generate_reply(sender_name, subject) client.inboxes.messages.reply( inbox_id=inbox_id, message_id=message_id, to=[sender_email], # ← Должен быть список! text=reply_text ) print(f"Автоответ отправлен на {sender_email}\n")except Exception as e: print(f"Ошибка: {e}\n")return Response(status=200) # Всегда возвращайте 200 для подтверждения веб-хука
Важные детали:
Параметр to должен быть списком адресов электронной почты
message_id связывает ответ с исходным письмом (потокировка)
Всегда возвращайте статус 200 для подтверждения веб-хука
Ошибки регистрируются, но не крушат сервер
Зачем всегда возвращать 200?Даже если отправка ответа не удается, мы возвращаем 200 AgentInbox. Это говорит AgentInbox “я получил и обработал этот веб-хук.” Если мы вернули бы статус ошибки, AgentInbox попытается отправить веб-хук несколько раз, что не полезно для ошибок приложения.
В течение нескольких секунд вы должны получить автоматический ответ:
Copy
Привет Youremail,Спасибо за ваше письмо! Я получил ваше сообщение и отвечу вам в течение 24 часов.Если ваше дело срочно, пожалуйста, ответьте с "СРОЧНО" в строке темы.С уважением,Агент автоответчик
Это работает! Вы только что создали и протестировали своего первого агента AgentInbox.Агент извлек ваше имя, персонализировал сообщение и отправил мгновенный ответ.
Хотите обновить вашего агента интеллектуальными, контекстно-зависимыми ответами? Вы можете добавить ответы на основе ИИ, используя OpenAI.Шаг 1: установите OpenAI
Шаг 3: добавьте функции истории потока и ответа ИИ в agent.pyПосле функции generate_reply() добавьте:
Copy
def get_thread_history(thread_id): """Получите историю беседы для потока.""" try: thread = client.threads.get(thread_id=thread_id) return thread.messages if hasattr(thread, 'messages') else [] except Exception as e: print(f"Failed to fetch thread history: {e}") return []def format_thread_for_ai(messages): """Отформатируйте сообщения потока в историю беседы для ИИ.""" conversation = [] for msg in messages: if hasattr(msg, 'from_'): sender = msg.from_ text = msg.text or msg.html or "" else: sender = msg.get('from_', '') or msg.get('from', '') text = msg.get('text', '') or msg.get('html', '') or msg.get('body', '') if '<' in sender and '>' in sender: sender = sender.split('<')[1].split('>')[0].strip() if text: conversation.append(f"From: {sender}\n{text}") return "\n\n---\n\n".join(reversed(conversation))def generate_ai_reply(sender_name, email_body, subject, thread_history=""): """Создайте ответ на основе ИИ, используя OpenAI с контекстом потока.""" try: 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}" response = openai_client.chat.completions.create( model="gpt-4o-mini", messages=[ { "role": "system", "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." }, { "role": "user", "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." } ], max_tokens=250, temperature=0.7 ) return response.choices[0].message.content except Exception as e: print(f"AI generation failed, using template: {e}") return generate_reply(sender_name, subject)
Шаг 4: обновите обработчик веб-хукаВ функции receive_webhook() добавьте извлечение thread_id и замените раздел создания ответа:
Copy
subject = message.get('subject', '(нет темы)') thread_id = message.get('thread_id', '') # Добавьте эту строку # Залогируйте входящее письмо print(f"Email from {sender_email}: {subject}") # Создайте и отправьте автоответ try: if USE_AI_REPLY: email_body = message.get('text', '') or message.get('body', '') # Получите историю потока для контекста thread_history = "" if thread_id: print(f"Fetching thread history for: {thread_id[:20]}...") messages = get_thread_history(thread_id) if messages: thread_history = format_thread_for_ai(messages) print(f"Found {len(messages)} messages in thread") reply_text = generate_ai_reply(sender_name, email_body, subject, thread_history) print("Using AI-generated reply with thread context") else: reply_text = generate_reply(sender_name, subject) print("Using template reply") client.inboxes.messages.reply( inbox_id=inbox_id, message_id=message_id, to=[sender_email], text=reply_text ) print(f"Auto-reply sent to {sender_email}\n") except Exception as e: print(f"Error: {e}\n")
Как это работает:
Агент получает весь поток электронной почты, используя client.threads.get(thread_id)
История потока отформатирована и передана OpenAI для контекстно-зависимых ответов
ИИ может ссылаться на предыдущие сообщения и предоставить более интеллектуальные ответы
Если API OpenAI не удается, используется шаблонный ответ
Результат:Ваш агент теперь имеет память о беседе. При ответе на последующие письма ИИ видит всю историю беседы и может предоставить контекстные, интеллектуальные ответы, которые ссылаются на предыдущие обмены, вместо общих автоответов.Если вы создадите что-то крутое с AgentInbox, мы будем рады это услышать. Поделитесь в нашем канале в Telegram.