> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://agentinbox.space/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://agentinbox.space/_mcp/server.

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

> Простой агент, который автоматически отвечает на входящие письма с персонализацией

## Обзор

Соберите агента автоответа: входящие письма обрабатываются автоматически. Пример для начинающих по 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..."
```

## Требования

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

> **Info**
>
> **Нужно:**
>
> * Python 3.8+
> * [Аккаунт Agent Inbox](https://agentinbox-ru.vercel.app/) и API-ключ
> * [Аккаунт ngrok](https://ngrok.com) (достаточно бесплатного тарифа)

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

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

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

```bash
mkdir auto-reply-agent
cd auto-reply-agent
```

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

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

#### Показать полный код agent.py

```python
"""
Агент автоответа на почту

Простой пример: бот автоответа на Agent Inbox.
Агент автоматически отвечает на входящие письма персонализированными сообщениями.
"""

import os
from dotenv import load_dotenv

# Load environment variables before importing Agent Inbox
load_dotenv()

from flask import Flask, request, Response
import ngrok
from agentinbox import Agentinbox
import threading

# Configuration
PORT = 8080
INBOX_USERNAME = os.getenv("INBOX_USERNAME", "auto-reply")
WEBHOOK_DOMAIN = os.getenv("WEBHOOK_DOMAIN")

# Initialize Flask app and Agent Inbox client
app = Flask(__name__)
client = Agentinbox()
processed_messages = set()  # Track processed message IDs to prevent duplicates


def setup_agentinbox():
    """Create inbox and webhook with idempotency."""
    print("Setting up Agent Inbox infrastructure...")

    # Create inbox (or get existing one)
    try:
        inbox = client.inboxes.create(
            username=INBOX_USERNAME,
            client_id=f"{INBOX_USERNAME}-inbox"
        )
        print(f"✓ Inbox created: {inbox.inbox_id}")
    except Exception as e:
        if "already exists" in str(e).lower():
            inbox_id = f"{INBOX_USERNAME}@agentinbox.space"
            class SimpleInbox:
                def __init__(self, inbox_id):
                    self.inbox_id = inbox_id
            inbox = SimpleInbox(inbox_id)
            print(f"✓ Using existing inbox: {inbox.inbox_id}")
        else:
            raise

    # Start ngrok tunnel
    listener = ngrok.forward(PORT, domain=WEBHOOK_DOMAIN, authtoken_from_env=True)

    # Create webhook (or get existing one)
    try:
        webhook = client.webhooks.create(
            url=f"{listener.url()}/webhook/inbound",
            event_types=["message.received"],
            inbox_ids=[inbox.inbox_id],
            client_id=f"{INBOX_USERNAME}-webhook"
        )
        print(f"✓ Webhook created")
    except Exception as e:
        if "already exists" in str(e).lower():
            print(f"Webhook already exists")
        else:
            raise

    print(f"\n✓ Setup complete!")
    print(f"Inbox: {inbox.inbox_id}")
    print(f"Webhook: {listener.url()}/webhook/inbound\n")

    return inbox, listener


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


def process_and_reply(message_id, inbox_id, from_field, subject, message):
    """Process incoming message and send reply in background."""
    # Extract sender email and name
    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'

    # Log incoming email
    print(f"Processing email from {sender_email}: {subject}")

    # Generate and send auto-reply
    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"Auto-reply sent to {sender_email}\n")
    except Exception as e:
        print(f"Error: {e}\n")


@app.route('/webhook/inbound', methods=['POST'])
def receive_webhook():
    """Webhook endpoint to receive incoming email notifications."""
    payload = request.json
    event_type = payload.get('type') or payload.get('event_type')

    # Ignore outgoing messages
    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', '')

    # Validate required fields
    if not message_id or not inbox_id or not from_field:
        return Response(status=200)

    # prevent duplicate
    if message_id in processed_messages:
        return Response(status=200)
    processed_messages.add(message_id)

    subject = message.get('subject', '(no subject)')

    # Process in background thread and return immediately
    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("AUTO-REPLY EMAIL AGENT")
    print("="*60 + "\n")

    inbox, listener = setup_agentinbox()

    print(f"Agent is ready!")
    print(f"Send emails to: {inbox.inbox_id}")
    print(f"\nWaiting for incoming emails...\n")

    app.run(port=PORT)
```

### Шаг 3: requirements.txt

Файл `requirements.txt`:

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

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

```bash
pip install -r requirements.txt
```

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

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

```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. Инициализация

```python
import os
from dotenv import load_dotenv

# Load environment variables FIRST
load_dotenv()

from flask import Flask, request, Response
import ngrok
from agentinbox import Agentinbox

# Initialize the Agent Inbox client
client = Agentinbox()  # Reads AGENTINBOX_API_KEY from environment
app = Flask(__name__)
```

**Важно:**

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

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

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

```python
def setup_agentinbox():
    """Create inbox and webhook with idempotency."""

    # Create inbox (or get existing one)
    try:
        inbox = client.inboxes.create(
            username=INBOX_USERNAME,
            client_id=f"{INBOX_USERNAME}-inbox"  # ← Idempotency key
        )
        print(f"✓ Inbox created: {inbox.inbox_id}")
    except Exception as e:
        if "already exists" in str(e).lower():
            # Inbox already exists, that's fine!
            inbox_id = f"{INBOX_USERNAME}@agentinbox.space"
            inbox = SimpleInbox(inbox_id)
```

**Зачем `client_id`?**

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

```python
    # Start ngrok tunnel
    listener = ngrok.forward(PORT, domain=WEBHOOK_DOMAIN, authtoken_from_env=True)

    # Create webhook
    webhook = client.webhooks.create(
        url=f"{listener.url()}/webhook/inbound",
        event_types=["message.received"],  # ← Only subscribe to incoming emails
        client_id=f"{INBOX_USERNAME}-webhook"  # ← Idempotency
    )
```

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

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

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

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

```python
@app.route('/webhook/inbound', methods=['POST'])
def receive_webhook():
    """Webhook endpoint to receive incoming email notifications."""
    payload = request.json
    event_type = payload.get('type') or payload.get('event_type')

    # Ignore outgoing messages (prevents infinite loops!)
    if event_type == 'message.sent':
        return Response(status=200)
```

> **Info**
>
> **Почему игнорируем `message.sent`?**
>
> После ответа агента приходит вебхук `message.sent`. Без фильтра агент воспримет свой ответ как новое письмо и уйдёт в бесконечный цикл.
>
> Ответ `200` значит: событие получено, обрабатывать его не нужно.

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

```python
    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', '')

    # Validate required fields
    if not message_id or not inbox_id or not from_field:
        return Response(status=200)  # Gracefully skip incomplete data
```

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

```json
{
  "type": "message.received",
  "message": {
    "message_id": "<abc123@agentinbox.space>",
    "inbox_id": "auto-reply@agentinbox.space",
    "from_": "John Doe <john@example.com>",
    "subject": "Hello",
    "text": "Email body content..."
  }
}
```

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

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

```python
# Extract sender email and name
if '<' in from_field and '>' in from_field:
    # Format: "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:
        # Name is empty or has comma, use email username
        sender_name = sender_email.split('@')[0].title()
else:
    # Format: "john@example.com"
    sender_email = from_field.strip()
    sender_name = sender_email.split('@')[0].title()
```

**Примеры:**

* `"John Doe <john@example.com>"` → имя: "John Doe", email: "[john@example.com](mailto:john@example.com)"
* `"john@example.com"` → имя: "John", email: "[john@example.com](mailto:john@example.com)"
* `"Last, First <name@example.com>"` → имя: "Name", email: "[name@example.com](mailto:name@example.com)"

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

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

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

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

```python
try:
    reply_text = generate_reply(sender_name, subject)
    client.inboxes.messages.reply(
        inbox_id=inbox_id,
        message_id=message_id,
        to=[sender_email],  # ← Must be a list!
        text=reply_text
    )
    print(f"Auto-reply sent to {sender_email}\n")
except Exception as e:
    print(f"Error: {e}\n")

return Response(status=200)  # Always return 200 to acknowledge webhook
```

**Детали:**

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

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

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

```bash
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
```

> **Success**
>
> **Готово.** Агент запущен и ждёт письма.
>
> Не закрывайте терминал — иначе агент остановится.

## Проверка

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

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

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
```

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

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

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

Доступны:

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

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

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

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

#### ModuleNotFoundError: No module named 'agentinbox'

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

**Решение:**

```bash
pip install -r requirements.txt
```

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

```bash
source venv/bin/activate  # macOS/Linux
venv\Scripts\activate     # Windows
```

#### Agent Inbox API Error: Unauthorized

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

**Что сделать:**

1. В `.env` корректный `AGENTINBOX_API_KEY`
2. Ключ действителен в [панели Agent Inbox](https://agentinbox-ru.vercel.app/)
3. Нет лишних пробелов или кавычек
4. `.env` лежит рядом с `agent.py`

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

```python
from agentinbox import Agentinbox
client = Agentinbox()
print(client.inboxes.list())  # Should succeed
```

#### Ngrok authentication failed

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

**Что сделать:**

1. Токен в [ngrok dashboard](https://dashboard.ngrok.com/get-started/your-authtoken)
2. Обновите `NGROK_AUTHTOKEN` в `.env`
3. Без лишних пробелов

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

```bash
ngrok config add-authtoken YOUR_TOKEN
```

#### Webhook not receiving emails

**Чеклист:**

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

**Отладка:**

1. Логируйте payload:

```python
@app.route('/webhook/inbound', methods=['POST'])
def receive_webhook():
    payload = request.json
    print(f"📨 Received webhook: {json.dumps(payload, indent=2)}")
    # ... rest of code ...
```

2. [Панель ngrok](https://dashboard.ngrok.com) — логи запросов

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

```python
client = Agentinbox()
webhooks = client.webhooks.list()
print(webhooks)
```

#### Port 8080 already in use

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

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

```bash
# macOS/Linux
lsof -ti:8080 | xargs kill -9

# Windows
netstat -ano | findstr :8080
taskkill /PID <PID> /F
```

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

```python
PORT = 8081  # Change in agent.py
```

#### Reply not being sent

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

**Шаги:**

1. Ошибки в консоли
2. Параметры reply API:

```python
print(f"Sending to: {sender_email}")
print(f"From inbox: {inbox_id}")
print(f"Reply text: {reply_text[:50]}...")
```

3. Прямой вызов reply API:

```python
client.inboxes.messages.reply(
    inbox_id="your-inbox@agentinbox.space",
    message_id="test-message-id",
    to=["test@example.com"],
    text="Test reply"
)
```

4. `to` должен быть списком:

```python
#  Wrong
to=sender_email

#  Correct
to=[sender_email]
```

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

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

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

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

```bash
pip install openai
```

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

```env
# AI Configuration
USE_AI_REPLY=true
OPENAI_API_KEY=sk-your_actual_openai_api_key_here
```

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

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

```python
def get_thread_history(thread_id):
    """Fetch conversation history for the thread."""
    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):
    """Format thread messages into conversation history for AI."""
    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=""):
    """Generate AI-powered reply using OpenAI with thread context."""
    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` и ветку с ИИ:

```python
    subject = message.get('subject', '(no subject)')
    thread_id = message.get('thread_id', '')  # Add this line

    # Log incoming email
    print(f"Email from {sender_email}: {subject}")

    # Generate and send auto-reply
    try:
        if USE_AI_REPLY:
            email_body = message.get('text', '') or message.get('body', '')

            # Fetch thread history for context
            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
* Ответы с учётом контекста
* При ошибке OpenAI в примере ниже можно добавить повторы (в тексте выше была неточность про «three times» — реализуйте ретраи при необходимости)

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

---

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