# Mail Server Прототип почтового сервера на **Spring Boot + Angular** с REST API для управления письмами. ## Архитектура ``` ┌─────────────────────────────────────────────────────┐ │ Angular Frontend │ │ (веб-почта: список, чтение, написание, контакты) │ └──────────────────────┬──────────────────────────────┘ │ REST API (JSON) ┌──────────────────────▼──────────────────────────────┐ │ Spring Boot Backend │ │ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │ │ │ Users │ │ Messages │ │ Contacts │ │ │ └────────────┘ └────────────┘ └──────────────┘ │ │ ┌────────────┐ ┌────────────┐ │ │ │ Folders │ │Attachments │ │ │ └────────────┘ └────────────┘ │ │ │ │ │ ┌────────▼────────┐ │ │ │ JavaMailSender │ │ │ │ (SMTP отправка)│ │ │ └─────────────────┘ │ └──────────────────────┬──────────────────────────────┘ │ JPA ┌──────────────────────▼──────────────────────────────┐ │ PostgreSQL 17 │ └─────────────────────────────────────────────────────┘ ``` ## Запуск ### 1. Docker Compose (рекомендуется) ```bash cd mail-server docker compose up --build ``` Сервисы: - **REST API**: http://localhost:8080 - **SMTP (приём)**: localhost:3025 - **IMAP (доступ к письмам)**: localhost:143 - **Mailpit UI**: http://localhost:8025 (SMTP на порту 1025) - **PostgreSQL**: localhost:5432 ### 2. Локально Требования: - Java 21 - PostgreSQL 17 ```bash # Запустите PostgreSQL docker run -d --name mail-db -p 5432:5432 \ -e POSTGRES_DB=mailserver \ -e POSTGRES_USER=mail \ -e POSTGRES_PASSWORD=secret \ postgres:17 # Запустите Mailpit для тестовой отправки docker run -d --name mailpit -p 1025:1025 -p 8025:8025 axllent/mailpit # Соберите и запустите бэкенд cd backend ./gradlew bootRun ``` ## API ### Тестовый пользователь При первом запуске приложения автоматически создаётся тестовый пользователь: - **Email:** `admin@mailserver.local` - **Пароль:** `admin123` ### Регистрация ```bash curl -X POST http://localhost:8080/api/register \ -H "Content-Type: application/json" \ -d '{"email":"test@example.com","password":"secret123","displayName":"Test User"}' ``` ### Логин ```bash curl -X POST http://localhost:8080/api/login \ -H "Content-Type: application/json" \ -d '{"email":"test@example.com","password":"secret123"}' ``` Ответ: ```json { "token": "eyJhbGci...", "email": "test@example.com" } ``` Все защищённые эндпоинты требуют заголовок `Authorization: Bearer `. ### Получить папки ```bash curl http://localhost:8080/api/folders \ -H "Authorization: Bearer " ``` ### Получить письма из папки ```bash curl "http://localhost:8080/api/messages?folderId=1&page=0&size=20" \ -H "Authorization: Bearer " ``` ### Отправить письмо ```bash curl -X POST http://localhost:8080/api/send \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"to":"recipient@example.com","subject":"Hello","body":"Test message"}' ``` ### Симулировать входящее письмо (для тестов) ```bash curl -X POST http://localhost:8080/api/simulate/receive \ -H "Content-Type: application/json" \ -d '{ "senderEmail":"sender@example.com", "senderName":"Sender", "recipientEmail":"test@example.com", "subject":"Incoming test", "body":"This is a test message" }' ``` ### Отметить как прочитанное ```bash curl -X PUT http://localhost:8080/api/messages/1/read \ -H "Authorization: Bearer " ``` ### Удалить письмо ```bash curl -X DELETE http://localhost:8080/api/messages/1 \ -H "Authorization: Bearer " ``` ### Загрузить вложение к письму ```bash curl -X POST http://localhost:8080/api/messages/1/attachments \ -H "Authorization: Bearer " \ -F "file=@/path/to/file.pdf" ``` ### Получить список вложений письма ```bash curl http://localhost:8080/api/messages/1/attachments \ -H "Authorization: Bearer " ``` ### Скачать вложение ```bash curl http://localhost:8080/api/attachments/1/download \ -H "Authorization: Bearer " \ -o file.pdf ``` ### Удалить вложение ```bash curl -X DELETE http://localhost:8080/api/attachments/1 \ -H "Authorization: Bearer " ``` ### Поиск по письмам Поиск поддерживает два режима: простой поиск по ключевому слову и расширенный поиск с отдельными фильтрами. **Простой поиск** — ищет по ключевому слову в теме, теле, отправителе и получателе: ```bash curl "http://localhost:8080/api/messages/search?folderId=1&keyword=hello" \ -H "Authorization: Bearer " ``` **Расширенный поиск** — используйте отдельные параметры для точной фильтрации: ```bash # Поиск по теме curl "http://localhost:8080/api/messages/search?folderId=1&subject=report" \ -H "Authorization: Bearer " # Поиск по тексту письма curl "http://localhost:8080/api/messages/search?folderId=1&body=invoice" \ -H "Authorization: Bearer " # Поиск по отправителю curl "http://localhost:8080/api/messages/search?folderId=1&sender=company.com" \ -H "Authorization: Bearer " # Комбинированный поиск (все фильтры применяются одновременно) curl "http://localhost:8080/api/messages/search?folderId=1&subject=report&sender=company.com" \ -H "Authorization: Bearer " ``` Все параметры поиска (`keyword`, `subject`, `body`, `sender`, `recipient`) необязательны. Если ни один параметр не указан, возвращаются все письма папки. ### Отправить письмо через SMTP-сервер Встроенный SMTP-сервер слушает порт **3025**. Любое письмо, отправленное на этот порт, будет сохранено в базе данных, если получатель зарегистрирован в системе. ```bash # Отправить письмо через telnet telnet localhost 3025 ``` Или с помощью curl: ```bash curl --url 'smtp://localhost:3025' \ --mail-from 'sender@example.com' \ --mail-rcpt 'test@example.com' \ -T <(echo -e 'From: sender@example.com\nTo: test@example.com\nSubject: SMTP Test\n\nHello from SMTP!') ``` Письмо появится во входящих (INBOX) пользователя `test@example.com`. ### Управление чёрным списком Добавьте отправителя в чёрный список — письма от него будут автоматически помечаться как спам. **Получить список заблокированных отправителей:** ```bash curl http://localhost:8080/api/spam/blacklist \ -H "Authorization: Bearer " ``` **Добавить отправителя в чёрный список:** ```bash curl -X POST http://localhost:8080/api/spam/blacklist \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"senderEmail":"spammer@example.com","reason":"Спам"}' ``` **Удалить отправителя из чёрного списка:** ```bash curl -X DELETE "http://localhost:8080/api/spam/blacklist?senderEmail=spammer@example.com" \ -H "Authorization: Bearer " ``` Письма от заблокированных отправителей автоматически получают флаг `spam: true` и могут быть отфильтрованы на стороне клиента. ### Подключение через IMAP-сервер Встроенный IMAP-сервер слушает порт **143**. Вы можете подключиться к нему из любого почтового клиента (Thunderbird, Outlook, Apple Mail) или через telnet для тестирования. ```bash # Подключение через telnet telnet localhost 143 ``` Пример сессии IMAP: ``` a LOGIN test@example.com secret123 a OK LOGIN completed a LIST "" "*" * LIST (\HasNoChildren) "." "INBOX" * LIST (\HasNoChildren) "." "Sent" a OK LIST completed a SELECT "INBOX" * 5 EXISTS * 0 RECENT * FLAGS (\Answered \Flagged \Deleted \Seen \Draft) a OK [READ-WRITE] SELECT completed a FETCH 1 (BODY[]) * 1 FETCH (UID 1 FLAGS (\Seen) BODY[] {123 Hello, this is a test message. )) a OK FETCH completed a SEARCH ALL * SEARCH 1 2 3 4 5 a OK SEARCH completed a LOGOUT * BYE IMAP4rev1 server terminating a OK LOGOUT completed ``` Поддерживаемые IMAP-команды: - **LOGIN** — аутентификация по email и паролю - **SELECT / EXAMINE** — выбор папки для работы - **FETCH** — получение заголовков и тела писем - **SEARCH** — поиск по письмам - **STORE** — изменение флагов (прочитано, помечено, удалено) - **EXPUNGE** — удаление помеченных писем - **LIST / LSUB** — список папок - **STATUS** — информация о папке (количество писем, непрочитанных) - **CAPABILITY** — список поддерживаемых возможностей - **LOGOUT** — выход из сессии Порт IMAP настраивается через `mailserver.imap.port` в `application.properties` (по умолчанию 143). ### DKIM / SPF / DMARC — защита от подделки писем Почтовый сервер поддерживает три стандарта защиты электронной почты: **DKIM (DomainKeys Identified Mail)** — криптографическая подпись писем. - При отправке письма автоматически подписываются RSA-ключом (2048 бит) - При получении входящих писем проверяется DKIM-подпись отправителя - Ключи хранятся в директории `./dkim-keys/` (генерируются автоматически при первом запуске) - Публичный ключ публикуется в DNS TXT-записи: `_default._domainkey.` **SPF (Sender Policy Framework)** — проверка разрешённых серверов отправителя. - При получении письма проверяется SPF DNS-запись домена отправителя - Поддерживаемые механизмы: `all`, `ip4`, `ip6`, `mx`, `a`, `include` - Локальные адреса (127.0.0.1, 192.168.x.x) пропускаются без проверки **DMARC (Domain-based Message Authentication, Reporting & Conformance)** — политика обработки. - Объединяет результаты SPF и DKIM с проверкой alignment (совпадение доменов) - Поддерживаемые политики: `none` (мониторинг), `quarantine` (спам), `reject` (отклонить) - Если SPF и DKIM не пройдены, применяется политика DMARC из DNS **Результат проверки:** - Письма, не прошедшие SPF/DKIM/DMARC, автоматически помечаются флагом `spam: true` - Логируются результаты всех трёх проверок для каждого входящего письма **Настройки в `application.properties`:** ``` mailserver.dkim.enabled=true mailserver.dkim.domain=localhost mailserver.dkim.keys-dir=./dkim-keys mailserver.spf.enabled=true mailserver.spf.local-bypass=true mailserver.dmarc.enabled=true mailserver.dmarc.local-bypass=true ``` **Публикация DKIM-ключа в DNS:** При первом запуске сервер выводит в лог публичный ключ DKIM. Добавьте его как TXT-запись: ``` _name: _default._domainkey.yourdomain.com type: TXT value: v=DKIM1; k=rsa; p=MIIBojANBgkqhkiG9w0BAQ... ``` ## Модели данных | Сущность | Описание | |---|---| | **User** | Почтовый ящик, пароль, квота | | **Folder** | INBOX, Sent, Drafts, Trash, Spam | | **Message** | Заголовки, тело, флаги (read, flagged, spam) | | **BlacklistEntry** | Заблокированный отправитель, привязка к пользователю | | **Attachment** | Имя, MIME-тип, размер, путь к файлу | | **Contact** | Имя, email, телефон, компания | ## Деплой на VPS ### Caddy Docker Proxy Проект использует **Caddy Docker Proxy** для автоматического HTTPS и reverse proxy. Caddy сам генерирует конфиг из `labels` в `docker-compose.prod.yml` — отдельный Caddyfile не нужен. **Как это работает:** - Контейнер `mail-server` имеет `labels` с настройками Caddy - `lucaslorentz/caddy-docker-proxy` следит за Docker через socket и автоматически создаёт виртуальные хосты - HTTPS-сертификат от Let's Encrypt получается автоматически **Ключевые labels:** | Label | Описание | |-------|----------| | `caddy: mail.jambotron.com` | Домен, на котором доступен сервис | | `caddy.route.api.reverse_proxy` | Проксирует `/api/*` на Spring Boot (порт 8080) | | `caddy.route.static.root` | Корневая директория для Angular фронтенда | | `caddy.route.static.file_server` | Раздаёт статические файлы Angular | **Запуск на VPS:** ```bash # Клонируйте репозиторий git clone https://gitea.jambotron.com/jambotron_gang/reactive-mail-server.git cd reactive-mail-server # Соберите фронтенд cd frontend && npm install && npx ng build --configuration=production cd .. # Запустите docker compose -f docker-compose.prod.yml up -d --build ``` **Перед запуском:** 1. Замените `mail.jambotron.com` на ваш домен в `docker-compose.prod.yml` 2. Настройте PTR (Reverse DNS) у хостера — IP → `mail.jambotron.com` 3. Добавьте DNS-записи: A (`mail` → IP), MX (`@` → `mail.jambotron.com`), TXT (`v=spf1 ip4: -all`) 4. Откройте порты: 80, 443, 25 (SMTP relay), 3025 (SMTP приём) ## TODO - [x] Хэширование паролей (BCrypt) - [x] Аутентификация (JWT) - [x] Приём SMTP (встроенный сервер на WebFlux TcpServer) - [x] IMAP-сервер (встроенный, порт 143) - [x] Вложения (загрузка/скачивание) - [x] Поиск по письмам (по теме, телу, отправителю, получателю) - [x] Angular фронтенд (v22 + Tailwind CSS + Synthwave дизайн) - [x] Антиспам (чёрный список отправителей) - [x] DKIM/SPF/DMARC (подпись, верификация, DNS-проверка) - [x] SMTP релей (доставка на внешнюю почту) - [x] Контакты (адресная книга) - [x] Reply / Forward - [x] Пагинация писем - [x] HTML/Markdown режим - [x] Caddy Docker Proxy (автоматический HTTPS) - [ ] TLS шифрование SMTP/IMAP ## Лицензия MIT