haskimail 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. haskimail-1.0.0/.github/workflows/publish.yml +46 -0
  2. haskimail-1.0.0/.gitignore +19 -0
  3. haskimail-1.0.0/CHANGELOG.md +14 -0
  4. haskimail-1.0.0/LICENSE +21 -0
  5. haskimail-1.0.0/PKG-INFO +380 -0
  6. haskimail-1.0.0/README.md +352 -0
  7. haskimail-1.0.0/examples/error_handling.py +43 -0
  8. haskimail-1.0.0/examples/manage_bounces.py +22 -0
  9. haskimail-1.0.0/examples/manage_domains.py +28 -0
  10. haskimail-1.0.0/examples/manage_templates.py +32 -0
  11. haskimail-1.0.0/examples/send_email.py +19 -0
  12. haskimail-1.0.0/examples/send_email_batch.py +28 -0
  13. haskimail-1.0.0/examples/send_with_attachments.py +28 -0
  14. haskimail-1.0.0/examples/send_with_template.py +19 -0
  15. haskimail-1.0.0/haskimail/__init__.py +42 -0
  16. haskimail-1.0.0/haskimail/account_client.py +141 -0
  17. haskimail-1.0.0/haskimail/client.py +106 -0
  18. haskimail-1.0.0/haskimail/exceptions.py +126 -0
  19. haskimail-1.0.0/haskimail/server_client.py +306 -0
  20. haskimail-1.0.0/haskimail.egg-info/PKG-INFO +380 -0
  21. haskimail-1.0.0/haskimail.egg-info/SOURCES.txt +47 -0
  22. haskimail-1.0.0/haskimail.egg-info/dependency_links.txt +1 -0
  23. haskimail-1.0.0/haskimail.egg-info/requires.txt +1 -0
  24. haskimail-1.0.0/haskimail.egg-info/top_level.txt +1 -0
  25. haskimail-1.0.0/haskimail.png +0 -0
  26. haskimail-1.0.0/pyproject.toml +55 -0
  27. haskimail-1.0.0/setup.cfg +4 -0
  28. haskimail-1.0.0/tests/__init__.py +0 -0
  29. haskimail-1.0.0/tests/conftest.py +46 -0
  30. haskimail-1.0.0/tests/test_account_client.py +112 -0
  31. haskimail-1.0.0/tests/test_server_client.py +182 -0
  32. haskimail-1.0.0/wiki/Home.md +31 -0
  33. haskimail-1.0.0/wiki/Push-/321/210/320/260/320/261/320/273/320/276/320/275/320/276/320/262.md +46 -0
  34. haskimail-1.0.0/wiki//320/222/320/265/320/261/321/205/321/203/320/272/320/270.md +75 -0
  35. haskimail-1.0.0/wiki//320/222/320/276/320/267/320/262/321/200/320/260/321/202/321/213-(Bounces).md +72 -0
  36. haskimail-1.0.0/wiki//320/224/320/276/320/274/320/265/320/275/321/213.md +87 -0
  37. haskimail-1.0.0/wiki//320/230/321/201/321/205/320/276/320/264/321/217/321/211/320/270/320/265-/321/201/320/276/320/276/320/261/321/211/320/265/320/275/320/270/321/217.md +51 -0
  38. haskimail-1.0.0/wiki//320/232/320/260/320/275/320/260/320/273/321/213-/321/201/320/276/320/276/320/261/321/211/320/265/320/275/320/270/320/271.md +77 -0
  39. haskimail-1.0.0/wiki//320/236/320/261/321/200/320/260/320/261/320/276/321/202/320/272/320/260-/320/276/321/210/320/270/320/261/320/276/320/272.md +99 -0
  40. haskimail-1.0.0/wiki//320/236/321/202/320/272/321/200/321/213/321/202/320/270/321/217-/320/270-/320/272/320/273/320/270/320/272/320/270.md +78 -0
  41. haskimail-1.0.0/wiki//320/236/321/202/320/277/321/200/320/260/320/262/320/272/320/260-/320/277/320/270/321/201/320/265/320/274.md +94 -0
  42. haskimail-1.0.0/wiki//320/237/320/260/320/272/320/265/321/202/320/275/320/260/321/217-/320/276/321/202/320/277/321/200/320/260/320/262/320/272/320/260.md +69 -0
  43. haskimail-1.0.0/wiki//320/237/320/276/320/264/320/277/320/270/321/201/320/270-/320/276/321/202/320/277/321/200/320/260/320/262/320/270/321/202/320/265/320/273/320/265/320/271.md +75 -0
  44. haskimail-1.0.0/wiki//320/241/320/265/321/200/320/262/320/265/321/200/321/213.md +59 -0
  45. haskimail-1.0.0/wiki//320/241/321/202/320/260/321/202/320/270/321/201/321/202/320/270/320/272/320/260.md +117 -0
  46. haskimail-1.0.0/wiki//320/241/321/202/320/276/320/277-/320/273/320/270/321/201/321/202/321/213-(Suppressions).md +58 -0
  47. haskimail-1.0.0/wiki//320/243/320/264/320/260/320/273/320/265/320/275/320/270/320/265-/320/264/320/260/320/275/320/275/321/213/321/205.md +29 -0
  48. haskimail-1.0.0/wiki//320/243/321/201/321/202/320/260/320/275/320/276/320/262/320/272/320/260-/320/270-/320/275/320/260/321/201/321/202/321/200/320/276/320/271/320/272/320/260.md +72 -0
  49. haskimail-1.0.0/wiki//320/250/320/260/320/261/320/273/320/276/320/275/321/213.md +126 -0
@@ -0,0 +1,46 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build distribution
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Set up Python
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.12"
18
+
19
+ - name: Install build tooling
20
+ run: python -m pip install --upgrade build
21
+
22
+ - name: Build sdist and wheel
23
+ run: python -m build
24
+
25
+ - name: Upload artifacts
26
+ uses: actions/upload-artifact@v4
27
+ with:
28
+ name: dist
29
+ path: dist/
30
+
31
+ publish:
32
+ name: Publish to PyPI
33
+ needs: build
34
+ runs-on: ubuntu-latest
35
+ environment: pypi
36
+ permissions:
37
+ id-token: write
38
+ steps:
39
+ - name: Download artifacts
40
+ uses: actions/download-artifact@v4
41
+ with:
42
+ name: dist
43
+ path: dist/
44
+
45
+ - name: Publish to PyPI
46
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,19 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ *.egg
9
+ .venv/
10
+ venv/
11
+ env/
12
+ .env
13
+ .mypy_cache/
14
+ .ruff_cache/
15
+ .pytest_cache/
16
+ htmlcov/
17
+ .coverage
18
+ *.so
19
+ .DS_Store
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 (2026-06-01)
4
+
5
+ Initial release.
6
+
7
+ - `ServerClient` with full API coverage: email sending, bounces, templates,
8
+ server management, outbound messages, opens, clicks, statistics,
9
+ webhooks, message streams, and suppressions.
10
+ - `AccountClient` with full API coverage: servers, domains, sender signatures,
11
+ template push, and data removals.
12
+ - Typed exception hierarchy matching the HTTP error codes from the Haskimail API.
13
+ - Context manager support for automatic session cleanup.
14
+ - Type hints throughout for IDE autocompletion.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dashamail
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,380 @@
1
+ Metadata-Version: 2.4
2
+ Name: haskimail
3
+ Version: 1.0.0
4
+ Summary: Official Python client for the Haskimail transactional email API
5
+ Author-email: Dashamail <support@haskimail.ru>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://haskimail.ru
8
+ Project-URL: Documentation, https://haskimail.ru/developer
9
+ Project-URL: Repository, https://github.com/Dashamail/haskimail-python
10
+ Project-URL: Issues, https://github.com/Dashamail/haskimail-python/issues
11
+ Keywords: haskimail,email,transactional,smtp,api
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Communications :: Email
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: requests>=2.28.0
27
+ Dynamic: license-file
28
+
29
+ <a href="https://haskimail.ru">
30
+ <img src="https://github.com/DashaMail/haskimail-python/raw/main/haskimail.png" alt="Haskimail Logo" title="Haskimail" width="120" height="120" align="right">
31
+ </a>
32
+
33
+ # Haskimail Python
34
+
35
+ Официальная Python-библиотека для [Haskimail HTTP API](https://haskimail.ru/developer).
36
+
37
+ [![PyPI version](https://img.shields.io/pypi/v/haskimail.svg)](https://pypi.org/project/haskimail/)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
39
+
40
+ ## Установка
41
+
42
+ ```bash
43
+ pip install haskimail
44
+ ```
45
+
46
+ ## Использование
47
+
48
+ ### Отправка письма
49
+
50
+ ```python
51
+ from haskimail import ServerClient
52
+
53
+ client = ServerClient("ваш-серверный-токен")
54
+
55
+ response = client.send_email({
56
+ "From": "отправитель@домен.ru",
57
+ "To": "получатель@домен.ru",
58
+ "Subject": "Привет от Haskimail!",
59
+ "HtmlBody": "<h1>Привет!</h1><p>Это письмо отправлено через Haskimail.</p>",
60
+ "TextBody": "Привет! Это письмо отправлено через Haskimail.",
61
+ "MessageStream": "outbound",
62
+ })
63
+
64
+ print(f"ID сообщения: {response['MessageID']}")
65
+ ```
66
+
67
+ ### Отправка с шаблоном
68
+
69
+ ```python
70
+ response = client.send_email_with_template({
71
+ "TemplateId": 42,
72
+ "From": "отправитель@домен.ru",
73
+ "To": "получатель@домен.ru",
74
+ "TemplateModel": {
75
+ "product_name": "Мой Сервис",
76
+ "user_name": "Иван",
77
+ },
78
+ })
79
+ ```
80
+
81
+ ### Пакетная отправка
82
+
83
+ ```python
84
+ # До 500 писем за один запрос
85
+ responses = client.send_email_batch([
86
+ {
87
+ "From": "отправитель@домен.ru",
88
+ "To": "user1@example.com",
89
+ "Subject": "Привет, User 1!",
90
+ "TextBody": "Сообщение для User 1",
91
+ },
92
+ {
93
+ "From": "отправитель@домен.ru",
94
+ "To": "user2@example.com",
95
+ "Subject": "Привет, User 2!",
96
+ "TextBody": "Сообщение для User 2",
97
+ },
98
+ ])
99
+ ```
100
+
101
+ ### Управление возвратами (bounces)
102
+
103
+ ```python
104
+ # Получить статистику доставки
105
+ stats = client.get_delivery_statistics()
106
+
107
+ # Список возвратов
108
+ bounces = client.get_bounces(count=50, type="HardBounce")
109
+
110
+ # Реактивировать получателя
111
+ client.activate_bounce(bounce_id=12345)
112
+ ```
113
+
114
+ ### Шаблоны
115
+
116
+ ```python
117
+ # Список шаблонов
118
+ templates = client.get_templates(count=100)
119
+
120
+ # Создать шаблон
121
+ template = client.create_template({
122
+ "Name": "Приветственное письмо",
123
+ "Subject": "Добро пожаловать, {{name}}!",
124
+ "HtmlBody": "<h1>Привет, {{name}}!</h1>",
125
+ "TextBody": "Привет, {{name}}!",
126
+ })
127
+
128
+ # Редактировать шаблон
129
+ client.edit_template(template["TemplateId"], {
130
+ "Subject": "Обновлённая тема",
131
+ })
132
+
133
+ # Валидировать шаблон
134
+ result = client.validate_template({
135
+ "Subject": "{{subject}}",
136
+ "HtmlBody": "{{content}}",
137
+ "TestRenderModel": {"subject": "Тест", "content": "Привет!"},
138
+ })
139
+ ```
140
+
141
+ ### Каналы сообщений (Message Streams)
142
+
143
+ ```python
144
+ # Список каналов
145
+ streams = client.get_message_streams()
146
+
147
+ # Создать канал
148
+ stream = client.create_message_stream({
149
+ "ID": "notifications",
150
+ "Name": "Уведомления",
151
+ "MessageStreamType": "Transactional",
152
+ })
153
+
154
+ # Архивировать канал
155
+ client.archive_message_stream("notifications")
156
+ ```
157
+
158
+ ### Стоп-листы (Suppressions)
159
+
160
+ ```python
161
+ # Получить стоп-лист для канала
162
+ suppressions = client.get_suppressions("outbound", SuppressionReason="HardBounce")
163
+
164
+ # Добавить в стоп-лист
165
+ client.create_suppressions("outbound", {
166
+ "Suppressions": [{"EmailAddress": "bad@example.com"}]
167
+ })
168
+
169
+ # Удалить из стоп-листа
170
+ client.delete_suppressions("outbound", {
171
+ "Suppressions": [{"EmailAddress": "bad@example.com"}]
172
+ })
173
+ ```
174
+
175
+ ### Вебхуки
176
+
177
+ ```python
178
+ # Создать вебхук
179
+ webhook = client.create_webhook({
180
+ "Url": "https://example.com/webhooks/haskimail",
181
+ "MessageStream": "outbound",
182
+ "Triggers": {
183
+ "Open": {"Enabled": True},
184
+ "Click": {"Enabled": True},
185
+ "Delivery": {"Enabled": True},
186
+ "Bounce": {"Enabled": True, "IncludeContent": True},
187
+ },
188
+ })
189
+
190
+ # Список вебхуков
191
+ webhooks = client.get_webhooks()
192
+ ```
193
+
194
+ ### Статистика
195
+
196
+ ```python
197
+ # Общий обзор
198
+ overview = client.get_outbound_overview(
199
+ fromdate="2026-01-01",
200
+ todate="2026-06-01",
201
+ messagestream="outbound",
202
+ )
203
+
204
+ # Отправки по дням
205
+ sent = client.get_sent_counts(tag="welcome")
206
+
207
+ # Открытия по платформам
208
+ platforms = client.get_email_open_platform_usage()
209
+
210
+ # Клики по браузерам
211
+ browsers = client.get_click_browser_usage()
212
+ ```
213
+
214
+ ## AccountClient
215
+
216
+ Для операций на уровне аккаунта (управление серверами, доменами, подписями отправителей) используйте `AccountClient`:
217
+
218
+ ```python
219
+ from haskimail import AccountClient
220
+
221
+ account = AccountClient("ваш-аккаунт-токен")
222
+ ```
223
+
224
+ ### Домены
225
+
226
+ ```python
227
+ # Список доменов
228
+ domains = account.get_domains()
229
+
230
+ # Добавить домен
231
+ domain = account.create_domain({"Name": "example.com"})
232
+
233
+ # Проверить DKIM
234
+ account.verify_domain_dkim(domain["ID"])
235
+
236
+ # Проверить SPF
237
+ account.verify_domain_spf(domain["ID"])
238
+
239
+ # Ротация DKIM-ключа
240
+ account.rotate_domain_dkim(domain["ID"])
241
+ ```
242
+
243
+ ### Серверы
244
+
245
+ ```python
246
+ # Список серверов
247
+ servers = account.get_servers()
248
+
249
+ # Создать сервер
250
+ server = account.create_server({
251
+ "Name": "Production",
252
+ "Color": "Green",
253
+ })
254
+
255
+ # Редактировать сервер
256
+ account.edit_server(server["ID"], {"Name": "Production v2"})
257
+ ```
258
+
259
+ ### Подписи отправителей
260
+
261
+ ```python
262
+ # Создать подпись
263
+ signature = account.create_sender_signature({
264
+ "Name": "Отдел продаж",
265
+ "FromEmail": "sales@example.com",
266
+ })
267
+
268
+ # Подтвердить SPF
269
+ account.verify_sender_signature_spf(signature["ID"])
270
+
271
+ # Запросить новый DKIM
272
+ account.request_new_dkim_for_sender_signature(signature["ID"])
273
+ ```
274
+
275
+ ### Push шаблонов
276
+
277
+ ```python
278
+ # Перенести шаблоны между серверами
279
+ account.push_templates({
280
+ "SourceServerID": 1,
281
+ "DestinationServerID": 2,
282
+ "PerformChanges": True,
283
+ })
284
+ ```
285
+
286
+ ### Удаление данных (GDPR)
287
+
288
+ ```python
289
+ result = account.request_data_removal({
290
+ "RequestedBy": "admin@example.com",
291
+ "RequestedFor": "user@example.com",
292
+ })
293
+
294
+ status = account.get_data_removal_status(result["ID"])
295
+ ```
296
+
297
+ ## Обработка ошибок
298
+
299
+ Библиотека использует типизированную иерархию исключений:
300
+
301
+ ```python
302
+ from haskimail import (
303
+ ServerClient,
304
+ HaskimailError, # Базовое исключение
305
+ HttpError, # Ошибка HTTP-уровня
306
+ InvalidAPIKeyError, # 401 — неверный или отсутствующий токен
307
+ ApiInputError, # 422 — ошибка в данных запроса
308
+ InactiveRecipientsError, # 422/406 — неактивный получатель
309
+ InvalidEmailRequestError, # 422/300 — неверный формат email
310
+ RateLimitExceededError, # 429 — превышен лимит запросов
311
+ InternalServerError, # 500 — ошибка сервера
312
+ ServiceUnavailableError, # 503 — сервис недоступен
313
+ UnknownError, # Другие HTTP-ошибки
314
+ )
315
+
316
+ client = ServerClient("ваш-токен")
317
+
318
+ try:
319
+ client.send_email({
320
+ "From": "sender@example.com",
321
+ "To": "recipient@example.com",
322
+ "Subject": "Test",
323
+ "TextBody": "Hello",
324
+ })
325
+ except InactiveRecipientsError as e:
326
+ print(f"Неактивные получатели: {e.recipients}")
327
+ except InvalidAPIKeyError:
328
+ print("Проверьте API-токен!")
329
+ except RateLimitExceededError:
330
+ print("Слишком много запросов, подождите...")
331
+ except ApiInputError as e:
332
+ print(f"Ошибка в данных: {e.message} (код: {e.error_code})")
333
+ except HttpError as e:
334
+ print(f"HTTP-ошибка {e.status_code}: {e.message}")
335
+ ```
336
+
337
+ ## Context Manager
338
+
339
+ Клиент можно использовать как контекстный менеджер для автоматического закрытия HTTP-сессии:
340
+
341
+ ```python
342
+ with ServerClient("ваш-токен") as client:
343
+ client.send_email({...})
344
+ # Сессия автоматически закрыта
345
+ ```
346
+
347
+ ## Тестирование
348
+
349
+ Для тестов используйте токен `HASKIMAIL_API_TEST` — письма будут валидированы, но не отправлены:
350
+
351
+ ```python
352
+ client = ServerClient("HASKIMAIL_API_TEST")
353
+ response = client.send_email({...}) # Не доставится, но проверит данные
354
+ ```
355
+
356
+ ## Настройка
357
+
358
+ ```python
359
+ client = ServerClient(
360
+ "ваш-токен",
361
+ base_url="https://api.haskimail.ru", # по умолчанию
362
+ timeout=30, # таймаут в секундах
363
+ )
364
+ ```
365
+
366
+ ## Требования
367
+
368
+ - Python 3.10+
369
+ - requests >= 2.28.0
370
+
371
+ ## Ссылки
372
+
373
+ - [Документация API Haskimail](https://haskimail.ru/developer)
374
+ - [haskimail.js](https://github.com/Dashamail/haskimail.js) — Node.js SDK
375
+ - [haskimail.java](https://github.com/Dashamail/haskimail.java) — Java SDK
376
+ - [haskimail-php](https://github.com/Dashamail/haskimail-php) — PHP SDK
377
+
378
+ ## Лицензия
379
+
380
+ MIT