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

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

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

## Обзор

Соберите агента классификации почты: ИИ анализирует письма и проставляет метки. Средний уровень: метки 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
```

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

> **Info**
>
> **Нужно:**
>
> * Python 3.8+
> * [Аккаунт Agent Inbox](https://agentinbox-ru.vercel.app/) и API-ключ
> * [Ключ OpenAI](https://platform.openai.com/api-keys) (классификация)
> * [Аккаунт ngrok](https://ngrok.com)

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

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

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

```bash
mkdir smart-labeling-agent
cd smart-labeling-agent
```

### Шаг 2: код `agent.py`

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

```python
"""
Smart Email Labeling Agent

An AI-powered email classification agent that automatically analyzes incoming
emails across multiple dimensions and applies appropriate labels.
"""

import os
import json
import time
from dotenv import load_dotenv

load_dotenv()

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

# Configuration
PORT = int(os.getenv("PORT", "8080"))
INBOX_USERNAME = os.getenv("INBOX_USERNAME", "smart-labels")
WEBHOOK_DOMAIN = os.getenv("WEBHOOK_DOMAIN")

# Initialize
app = Flask(__name__)
client = Agentinbox()
openai_client = OpenAI()


def setup_agentinbox():
    """Create inbox and webhook with idempotency."""
    # Create inbox
    try:
        inbox = client.inboxes.create(
            username=INBOX_USERNAME,
            client_id=f"{INBOX_USERNAME}-inbox"
        )
    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)
        else:
            raise

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

    # Create webhook
    try:
        client.webhooks.create(
            url=f"{listener.url()}/webhook/inbound",
            event_types=["message.received"],
            client_id=f"{INBOX_USERNAME}-webhook"
        )
    except Exception as e:
        if "already exists" not in str(e).lower():
            raise

    print(f"Ready: {inbox.inbox_id}\n")
    return inbox, listener


def analyze_email(subject, content):
    """Use AI to classify email across multiple dimensions with retry logic."""
    valid_values = {
        "sentiment": {"positive", "neutral", "negative"},
        "category": {"question", "complaint", "feature-request", "bug-report", "praise"},
        "priority": {"urgent", "high", "normal", "low"},
        "department": {"sales", "support", "billing", "technical"}
    }

    for attempt in range(1, 4):
        try:
            if attempt > 1:
                time.sleep(1)

            response = openai_client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[
                    {
                        "role": "system",
                        "content": "You are an expert email classifier. Analyze emails and return structured classifications."
                    },
                    {
                        "role": "user",
                        "content": f"""Analyze this email across 4 dimensions:

                        Subject: {subject}
                        Content: {content}

                        Classify into:
                        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

                        Consider:
                        - Sentiment: Overall tone and emotion
                        - Category: Primary intent of the email
                        - Priority: Urgency indicators (ASAP, urgent, immediately, deadline mentions, emergency)
                        - Department: Best team to handle this

                        Return ONLY valid JSON with these exact keys: sentiment, category, priority, department.
                        Example: {{"sentiment": "positive", "category": "question", "priority": "normal", "department": "sales"}}
                        """
                    }
                ],
                response_format={"type": "json_object"},
                temperature=0.3
            )

            # Parse and validate
            result = json.loads(response.choices[0].message.content)

            required_keys = ["sentiment", "category", "priority", "department"]
            missing_keys = [key for key in required_keys if key not in result]
            if missing_keys:
                raise ValueError(f"Missing keys: {missing_keys}")

            invalid_values = []
            for dimension, value in result.items():
                if dimension in valid_values and value not in valid_values[dimension]:
                    invalid_values.append(f"{dimension}={value}")

            if invalid_values:
                raise ValueError(f"Invalid values: {', '.join(invalid_values)}")

            return result

        except Exception as e:
            if attempt == 3:
                raise Exception(f"AI classification failed: {e}")


def apply_labels(inbox_id, message_id, classifications):
    """Apply labels based on classification results."""
    labels = [
        f"{classifications['sentiment']}",
        f"{classifications['category']}",
        f"{classifications['priority']}",
        f"{classifications['department']}"
    ]

    # Try batch first
    try:
        client.inboxes.messages.update(
            inbox_id=inbox_id,
            message_id=message_id,
            add_labels=labels
        )
        for label in labels:
            print(f"  ✓ {label}")
        return
    except Exception:
        pass

    # Try individually
    successful = []
    for label in labels:
        try:
            client.inboxes.messages.update(
                inbox_id=inbox_id,
                message_id=message_id,
                add_labels=[label]
            )
            successful.append(label)
            print(f"  ✓ {label}")
        except Exception:
            print(f"  ✗ {label}")

    if not successful:
        raise Exception("Failed to apply labels")


@app.route('/webhook/inbound', methods=['POST'])
def receive_webhook():
    """Webhook endpoint to receive incoming email notifications."""
    try:
        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)

        # Extract sender email
        if '<' in from_field and '>' in from_field:
            sender_email = from_field.split('<')[1].split('>')[0].strip()
        else:
            sender_email = from_field.strip()

        subject = message.get('subject', '(no subject)')
        email_body = message.get('text', '') or message.get('body', '') or message.get('html', '')

        # Log
        print(f"\n📧 {sender_email}: {subject}")

        # Analyze
        classifications = analyze_email(subject, email_body)

        print(f"  Sentiment: {classifications['sentiment']}")
        print(f"  Category: {classifications['category']}")
        print(f"  Priority: {classifications['priority']}")
        print(f"  Department: {classifications['department']}")

        # Apply labels
        apply_labels(inbox_id, message_id, classifications)
        print("Done\n")

    except Exception as e:
        print(f"Error: {e}\n")

    return Response(status=200)


if __name__ == '__main__':
    print("SMART EMAIL LABELING AGENT\n")
    inbox, listener = setup_agentinbox()
    print("Waiting for emails...\n")
    app.run(port=PORT)
```

### Шаг 3: `requirements.txt`

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

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

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

### Шаг 5: `.env`

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

```python
# Load environment variables first
load_dotenv()

# Initialize three clients
app = Flask(__name__)           # Web server for webhooks
client = Agentinbox()            # Agent Inbox SDK
openai_client = OpenAI()        # OpenAI for AI classification
```

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

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

```python
# Create inbox with idempotency
inbox = client.inboxes.create(
    username=INBOX_USERNAME,
    client_id=f"{INBOX_USERNAME}-inbox"  # Prevents duplicates
)

# Start ngrok tunnel (localhost → public URL)
listener = ngrok.forward(PORT, domain=WEBHOOK_DOMAIN)

# Register webhook with Agent Inbox
client.webhooks.create(
    url=f"{listener.url()}/webhook/inbound",
    event_types=["message.received"],
    client_id=f"{INBOX_USERNAME}-webhook"
)
```

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

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

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

```python
def analyze_email(subject, content):
    # Define valid classification values
    valid_values = {
        "sentiment": {"positive", "neutral", "negative"},
        "category": {"question", "complaint", "feature-request", ...},
        "priority": {"urgent", "high", "normal", "low"},
        "department": {"sales", "support", "billing", "technical"}
    }

    # Retry up to 3 times
    for attempt in range(1, 4):
        try:
            # Call OpenAI API
            response = openai_client.chat.completions.create(...)

            # Parse JSON response
            result = json.loads(response.choices[0].message.content)

            # Validate keys and values
            # ... validation logic ...

            return result
        except Exception as e:
            if attempt == 3:
                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...
```

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

```json
{
  "sentiment": "negative",
  "category": "complaint",
  "priority": "urgent",
  "department": "support"
}
```

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

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

```python
# Create labels from classification values
labels = [
    "negative",
    "complaint",
    "urgent",
    "support"
]

# Try batch application first (most efficient)
try:
    client.inboxes.messages.update(
        inbox_id=inbox_id,
        message_id=message_id,
        add_labels=labels  # Apply all at once
    )
    return
except Exception:
    pass  # If batch fails, try individually

# Apply labels one by one
for label in labels:
    try:
        client.inboxes.messages.update(
            inbox_id=inbox_id,
            message_id=message_id,
            add_labels=[label]  # One at a time
        )
    except Exception:
        pass  # Log failure but continue
```

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

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

### 5. Вебхук

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

```python
@app.route('/webhook/inbound', methods=['POST'])
def receive_webhook():
    payload = request.json

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

    # Extract email data
    message_id = message.get('message_id')
    inbox_id = message.get('inbox_id')
    sender_email = extract_email(from_field)
    subject = message.get('subject')
    email_body = message.get('text')

    # Classify with AI
    classifications = analyze_email(subject, email_body)

    # Apply labels
    apply_labels(inbox_id, message_id, classifications)

    # Always return 200 (tells Agent Inbox we received it)
    return Response(status=200)
```

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

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

## Запуск

```bash
python agent.py
```

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

```
SMART EMAIL LABELING AGENT

Ready: smart-labels@agentinbox.space

Waiting for emails...

 * Running on http://127.0.0.1:8080
```

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

## Проверка

### Пример 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](/_fern-files/_img/58630b8f4fda508ac0e6baf8df2c5a3abb992f27f0c0ab1f0642bb8d76f355a7.webp)

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

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

> **Success**
>
> **Совет:** через 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 и аналитику.