coffeemail 0.1.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.
- coffeemail-0.1.0/.github/workflows/ci.yml +42 -0
- coffeemail-0.1.0/.github/workflows/publish.yml +61 -0
- coffeemail-0.1.0/.gitignore +69 -0
- coffeemail-0.1.0/PKG-INFO +179 -0
- coffeemail-0.1.0/README.md +147 -0
- coffeemail-0.1.0/pyproject.toml +97 -0
- coffeemail-0.1.0/specs/SDD_ROADMAP.md +98 -0
- coffeemail-0.1.0/specs/SPEC.md +142 -0
- coffeemail-0.1.0/src/coffeemail/__init__.py +106 -0
- coffeemail-0.1.0/src/coffeemail/async_client.py +52 -0
- coffeemail-0.1.0/src/coffeemail/client.py +50 -0
- coffeemail-0.1.0/src/coffeemail/core/crypto.py +19 -0
- coffeemail-0.1.0/src/coffeemail/core/errors.py +88 -0
- coffeemail-0.1.0/src/coffeemail/core/http.py +243 -0
- coffeemail-0.1.0/src/coffeemail/core/i18n.py +33 -0
- coffeemail-0.1.0/src/coffeemail/core/types.py +50 -0
- coffeemail-0.1.0/src/coffeemail/models/__init__.py +89 -0
- coffeemail-0.1.0/src/coffeemail/models/audiences.py +50 -0
- coffeemail-0.1.0/src/coffeemail/models/broadcasts.py +27 -0
- coffeemail-0.1.0/src/coffeemail/models/domains.py +43 -0
- coffeemail-0.1.0/src/coffeemail/models/emails.py +96 -0
- coffeemail-0.1.0/src/coffeemail/models/stats.py +16 -0
- coffeemail-0.1.0/src/coffeemail/models/suppressions.py +29 -0
- coffeemail-0.1.0/src/coffeemail/models/templates.py +31 -0
- coffeemail-0.1.0/src/coffeemail/models/webhooks.py +47 -0
- coffeemail-0.1.0/src/coffeemail/py.typed +1 -0
- coffeemail-0.1.0/src/coffeemail/resources/__init__.py +29 -0
- coffeemail-0.1.0/src/coffeemail/resources/audiences.py +307 -0
- coffeemail-0.1.0/src/coffeemail/resources/broadcasts.py +154 -0
- coffeemail-0.1.0/src/coffeemail/resources/domains.py +99 -0
- coffeemail-0.1.0/src/coffeemail/resources/emails.py +243 -0
- coffeemail-0.1.0/src/coffeemail/resources/stats.py +49 -0
- coffeemail-0.1.0/src/coffeemail/resources/suppressions.py +108 -0
- coffeemail-0.1.0/src/coffeemail/resources/templates.py +257 -0
- coffeemail-0.1.0/src/coffeemail/resources/webhooks.py +182 -0
- coffeemail-0.1.0/tests/conftest.py +6 -0
- coffeemail-0.1.0/tests/test_client.py +54 -0
- coffeemail-0.1.0/tests/test_crypto.py +30 -0
- coffeemail-0.1.0/tests/test_emails.py +40 -0
- coffeemail-0.1.0/uv.lock +751 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
validate:
|
|
11
|
+
name: Validate (lint + typecheck + test + build)
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v5
|
|
19
|
+
with:
|
|
20
|
+
version: "latest"
|
|
21
|
+
enable-cache: true
|
|
22
|
+
|
|
23
|
+
- name: Set up Python
|
|
24
|
+
run: uv python install 3.10
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: uv sync --extra dev
|
|
28
|
+
|
|
29
|
+
- name: Check formatting with Ruff
|
|
30
|
+
run: uv run ruff format --check .
|
|
31
|
+
|
|
32
|
+
- name: Lint with Ruff
|
|
33
|
+
run: uv run ruff check .
|
|
34
|
+
|
|
35
|
+
- name: Typecheck with Mypy
|
|
36
|
+
run: uv run mypy src tests
|
|
37
|
+
|
|
38
|
+
- name: Run Tests
|
|
39
|
+
run: uv run pytest -v
|
|
40
|
+
|
|
41
|
+
- name: Build distribution package
|
|
42
|
+
run: uv build
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags:
|
|
7
|
+
- "v*"
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
validate-and-publish:
|
|
11
|
+
name: Build, verify and publish to PyPI
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
id-token: write # Necessário para Trusted Publishing (OIDC) no PyPI
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v5
|
|
22
|
+
with:
|
|
23
|
+
version: "latest"
|
|
24
|
+
enable-cache: true
|
|
25
|
+
|
|
26
|
+
- name: Set up Python
|
|
27
|
+
run: uv python install 3.10
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: uv sync --extra dev
|
|
31
|
+
|
|
32
|
+
- name: Code quality and tests
|
|
33
|
+
run: |
|
|
34
|
+
uv run ruff format --check .
|
|
35
|
+
uv run ruff check .
|
|
36
|
+
uv run mypy src tests
|
|
37
|
+
uv run pytest -v
|
|
38
|
+
|
|
39
|
+
- name: Check if current version is already on PyPI
|
|
40
|
+
id: check_version
|
|
41
|
+
run: |
|
|
42
|
+
VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
43
|
+
echo "Package version: $VERSION"
|
|
44
|
+
STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/coffeemail/$VERSION/json")
|
|
45
|
+
if [ "$STATUS_CODE" = "200" ]; then
|
|
46
|
+
echo "Version $VERSION is already published on PyPI. Skipping publication."
|
|
47
|
+
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
|
48
|
+
else
|
|
49
|
+
echo "New version $VERSION ready for publication."
|
|
50
|
+
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
- name: Build distribution packages (wheel and sdist)
|
|
54
|
+
if: steps.check_version.outputs.should_publish == 'true'
|
|
55
|
+
run: uv build
|
|
56
|
+
|
|
57
|
+
- name: Publish to PyPI via Trusted Publishing
|
|
58
|
+
if: steps.check_version.outputs.should_publish == 'true'
|
|
59
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
60
|
+
with:
|
|
61
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# Virtual environments
|
|
30
|
+
.venv/
|
|
31
|
+
env/
|
|
32
|
+
venv/
|
|
33
|
+
ENV/
|
|
34
|
+
env.bak/
|
|
35
|
+
venv.bak/
|
|
36
|
+
|
|
37
|
+
# Testing and coverage
|
|
38
|
+
.tox/
|
|
39
|
+
.nox/
|
|
40
|
+
.coverage
|
|
41
|
+
.coverage.*
|
|
42
|
+
.cache
|
|
43
|
+
nosetests.xml
|
|
44
|
+
coverage.xml
|
|
45
|
+
*.cover
|
|
46
|
+
*.py,cover
|
|
47
|
+
.hypothesis/
|
|
48
|
+
.pytest_cache/
|
|
49
|
+
cover/
|
|
50
|
+
|
|
51
|
+
# Type checking & Linters
|
|
52
|
+
.mypy_cache/
|
|
53
|
+
.dmypy.json
|
|
54
|
+
dmypy.json
|
|
55
|
+
.pyre/
|
|
56
|
+
.pytype/
|
|
57
|
+
.ruff_cache/
|
|
58
|
+
|
|
59
|
+
# IDE & OS
|
|
60
|
+
.DS_Store
|
|
61
|
+
.idea/
|
|
62
|
+
.vscode/
|
|
63
|
+
*.sublime-project
|
|
64
|
+
*.sublime-workspace
|
|
65
|
+
|
|
66
|
+
# Environment variables
|
|
67
|
+
.env
|
|
68
|
+
.env.*
|
|
69
|
+
!.env.example
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: coffeemail
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SDK oficial do CoffeeMail para Python (Síncrono e Assíncrono com tipagem estrita).
|
|
5
|
+
Project-URL: Homepage, https://coffeemail.com.br
|
|
6
|
+
Project-URL: Documentation, https://docs.coffeemail.com.br
|
|
7
|
+
Project-URL: Repository, https://github.com/coffeemail/coffee-mail-sdk-python
|
|
8
|
+
Author-email: CoffeeMail Team <contato@coffeemail.com.br>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: api,coffeemail,email,sdk,transactional
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Communications :: Email
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: httpx>=0.27.0
|
|
22
|
+
Requires-Dist: pydantic>=2.6.0
|
|
23
|
+
Requires-Dist: typing-extensions>=4.10.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy>=1.9.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-mock>=3.12.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: respx>=0.21.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# CoffeeMail Python SDK (`coffeemail`)
|
|
34
|
+
|
|
35
|
+
SDK oficial da **CoffeeMail** para Python, com suporte nativo a operações **Síncronas** e **Assíncronas**, tipagem estrita via **Pydantic v2**, compatibilidade total com PEP 561 (`py.typed`) e padrão de retorno seguro `{ data, error }`.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 📦 Instalação
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install coffeemail
|
|
43
|
+
# ou utilizando uv:
|
|
44
|
+
uv add coffeemail
|
|
45
|
+
# ou utilizando poetry:
|
|
46
|
+
poetry add coffeemail
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## ⚡ Início Rápido
|
|
52
|
+
|
|
53
|
+
### 1. Envio Transacional Síncrono (Django, Flask, Scripts)
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from coffeemail import CoffeeMail
|
|
57
|
+
|
|
58
|
+
# Lê automaticamente a variável de ambiente COFFEEMAIL_API_KEY se omitida:
|
|
59
|
+
client = CoffeeMail()
|
|
60
|
+
|
|
61
|
+
data, error = client.emails.send(
|
|
62
|
+
{
|
|
63
|
+
"from": "contato@seudominio.com.br",
|
|
64
|
+
"to": "cliente@empresa.com",
|
|
65
|
+
"subject": "Boas-vindas ao CoffeeMail!",
|
|
66
|
+
"html": "<h1>Olá, seja bem-vindo!</h1>",
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if error:
|
|
71
|
+
print(f"Erro ao disparar e-mail: {error.message}")
|
|
72
|
+
else:
|
|
73
|
+
print(f"E-mail enfileirado com sucesso! ID: {data.id}")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Envio Assíncrono de Alta Performance (FastAPI, Celery, Temporal)
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import asyncio
|
|
80
|
+
from coffeemail import AsyncCoffeeMail
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
async def main():
|
|
84
|
+
client = AsyncCoffeeMail()
|
|
85
|
+
|
|
86
|
+
response = await client.emails.send(
|
|
87
|
+
{
|
|
88
|
+
"from": {"email": "notificacoes@seudominio.com.br", "name": "Notificações"},
|
|
89
|
+
"to": "destinatario@gmail.com",
|
|
90
|
+
"subject": "Sua fatura foi gerada",
|
|
91
|
+
"html": "<p>Acesse o painel para visualizar o boleto.</p>",
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if response.error:
|
|
96
|
+
print(f"Falha: {response.error}")
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
print(f"Status do disparo: {response.data.status}")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
asyncio.run(main())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 🛡️ Padrão de Resposta Seguro e Ergonomia
|
|
108
|
+
|
|
109
|
+
Todas as chamadas do SDK retornam uma instância de `CoffeeMailResponse[T]`. Você pode optar pelo estilo que melhor se adapta à sua equipe:
|
|
110
|
+
|
|
111
|
+
### Desempacotamento de Tupla (Recomendado)
|
|
112
|
+
```python
|
|
113
|
+
data, error = client.emails.get("msg_123")
|
|
114
|
+
if error:
|
|
115
|
+
print(f"Falha: {error}")
|
|
116
|
+
else:
|
|
117
|
+
print(f"Status: {data.status}")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Método `.unwrap()` (Lança Exceção em Caso de Erro)
|
|
121
|
+
```python
|
|
122
|
+
try:
|
|
123
|
+
email = client.emails.get("msg_123").unwrap()
|
|
124
|
+
print(f"Entregue em: {email.delivered_at}")
|
|
125
|
+
except CoffeeMailError as err:
|
|
126
|
+
print(f"Exceção capturada: {err}")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 🧩 Recursos Disponíveis
|
|
132
|
+
|
|
133
|
+
* **`emails`**: Disparo individual, em lote (`send_batch`), consulta de status, cancelamento de agendamentos e listagem de eventos.
|
|
134
|
+
* **`domains`**: Criação de domínios e verificação de registros DNS (SPF, DKIM, DMARC, MX).
|
|
135
|
+
* **`templates`**: Cadastro de modelos HTML, renderização de pré-visualização (`preview`) e envio de testes.
|
|
136
|
+
* **`audiences`**: Listas de contatos, inclusão em lote (`bulk_add`) e gestão de inscritos.
|
|
137
|
+
* **`broadcasts`**: Campanhas e envios em massa com agendamento.
|
|
138
|
+
* **`suppressions`**: Consulta e gerenciamento de lista de supressão (bounces e unsubscribes).
|
|
139
|
+
* **`webhooks`**: Gerenciamento de endpoints e validação criptográfica de assinaturas HMAC SHA-256.
|
|
140
|
+
* **`stats`**: Consulta de taxas de entrega, abertura e rejeição por período.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 🔒 Validação Criptográfica de Webhooks
|
|
145
|
+
|
|
146
|
+
Proteja suas rotas contra requisições forjadas validando a assinatura enviada no header `x-coffeemail-signature`:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from coffeemail import Webhooks
|
|
150
|
+
|
|
151
|
+
is_valid = Webhooks.verify_signature(
|
|
152
|
+
payload=request_body_raw,
|
|
153
|
+
signature=headers.get("x-coffeemail-signature"),
|
|
154
|
+
secret="whsec_seu_segredo_cadastrado",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if not is_valid:
|
|
158
|
+
return {"error": "Assinatura inválida"}, 401
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 🛠️ Desenvolvimento e Testes
|
|
164
|
+
|
|
165
|
+
Para contribuir ou rodar os testes localmente:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Instalar dependências de desenvolvimento com uv:
|
|
169
|
+
uv sync --extra dev
|
|
170
|
+
|
|
171
|
+
# Executar suíte de testes unitários:
|
|
172
|
+
uv run pytest
|
|
173
|
+
|
|
174
|
+
# Executar checagem estrita de tipos com mypy:
|
|
175
|
+
uv run mypy src
|
|
176
|
+
|
|
177
|
+
# Executar linter e formatação com ruff:
|
|
178
|
+
uv run ruff check .
|
|
179
|
+
```
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# CoffeeMail Python SDK (`coffeemail`)
|
|
2
|
+
|
|
3
|
+
SDK oficial da **CoffeeMail** para Python, com suporte nativo a operações **Síncronas** e **Assíncronas**, tipagem estrita via **Pydantic v2**, compatibilidade total com PEP 561 (`py.typed`) e padrão de retorno seguro `{ data, error }`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📦 Instalação
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install coffeemail
|
|
11
|
+
# ou utilizando uv:
|
|
12
|
+
uv add coffeemail
|
|
13
|
+
# ou utilizando poetry:
|
|
14
|
+
poetry add coffeemail
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## ⚡ Início Rápido
|
|
20
|
+
|
|
21
|
+
### 1. Envio Transacional Síncrono (Django, Flask, Scripts)
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from coffeemail import CoffeeMail
|
|
25
|
+
|
|
26
|
+
# Lê automaticamente a variável de ambiente COFFEEMAIL_API_KEY se omitida:
|
|
27
|
+
client = CoffeeMail()
|
|
28
|
+
|
|
29
|
+
data, error = client.emails.send(
|
|
30
|
+
{
|
|
31
|
+
"from": "contato@seudominio.com.br",
|
|
32
|
+
"to": "cliente@empresa.com",
|
|
33
|
+
"subject": "Boas-vindas ao CoffeeMail!",
|
|
34
|
+
"html": "<h1>Olá, seja bem-vindo!</h1>",
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if error:
|
|
39
|
+
print(f"Erro ao disparar e-mail: {error.message}")
|
|
40
|
+
else:
|
|
41
|
+
print(f"E-mail enfileirado com sucesso! ID: {data.id}")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Envio Assíncrono de Alta Performance (FastAPI, Celery, Temporal)
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import asyncio
|
|
48
|
+
from coffeemail import AsyncCoffeeMail
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def main():
|
|
52
|
+
client = AsyncCoffeeMail()
|
|
53
|
+
|
|
54
|
+
response = await client.emails.send(
|
|
55
|
+
{
|
|
56
|
+
"from": {"email": "notificacoes@seudominio.com.br", "name": "Notificações"},
|
|
57
|
+
"to": "destinatario@gmail.com",
|
|
58
|
+
"subject": "Sua fatura foi gerada",
|
|
59
|
+
"html": "<p>Acesse o painel para visualizar o boleto.</p>",
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if response.error:
|
|
64
|
+
print(f"Falha: {response.error}")
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
print(f"Status do disparo: {response.data.status}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
asyncio.run(main())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 🛡️ Padrão de Resposta Seguro e Ergonomia
|
|
76
|
+
|
|
77
|
+
Todas as chamadas do SDK retornam uma instância de `CoffeeMailResponse[T]`. Você pode optar pelo estilo que melhor se adapta à sua equipe:
|
|
78
|
+
|
|
79
|
+
### Desempacotamento de Tupla (Recomendado)
|
|
80
|
+
```python
|
|
81
|
+
data, error = client.emails.get("msg_123")
|
|
82
|
+
if error:
|
|
83
|
+
print(f"Falha: {error}")
|
|
84
|
+
else:
|
|
85
|
+
print(f"Status: {data.status}")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Método `.unwrap()` (Lança Exceção em Caso de Erro)
|
|
89
|
+
```python
|
|
90
|
+
try:
|
|
91
|
+
email = client.emails.get("msg_123").unwrap()
|
|
92
|
+
print(f"Entregue em: {email.delivered_at}")
|
|
93
|
+
except CoffeeMailError as err:
|
|
94
|
+
print(f"Exceção capturada: {err}")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 🧩 Recursos Disponíveis
|
|
100
|
+
|
|
101
|
+
* **`emails`**: Disparo individual, em lote (`send_batch`), consulta de status, cancelamento de agendamentos e listagem de eventos.
|
|
102
|
+
* **`domains`**: Criação de domínios e verificação de registros DNS (SPF, DKIM, DMARC, MX).
|
|
103
|
+
* **`templates`**: Cadastro de modelos HTML, renderização de pré-visualização (`preview`) e envio de testes.
|
|
104
|
+
* **`audiences`**: Listas de contatos, inclusão em lote (`bulk_add`) e gestão de inscritos.
|
|
105
|
+
* **`broadcasts`**: Campanhas e envios em massa com agendamento.
|
|
106
|
+
* **`suppressions`**: Consulta e gerenciamento de lista de supressão (bounces e unsubscribes).
|
|
107
|
+
* **`webhooks`**: Gerenciamento de endpoints e validação criptográfica de assinaturas HMAC SHA-256.
|
|
108
|
+
* **`stats`**: Consulta de taxas de entrega, abertura e rejeição por período.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 🔒 Validação Criptográfica de Webhooks
|
|
113
|
+
|
|
114
|
+
Proteja suas rotas contra requisições forjadas validando a assinatura enviada no header `x-coffeemail-signature`:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from coffeemail import Webhooks
|
|
118
|
+
|
|
119
|
+
is_valid = Webhooks.verify_signature(
|
|
120
|
+
payload=request_body_raw,
|
|
121
|
+
signature=headers.get("x-coffeemail-signature"),
|
|
122
|
+
secret="whsec_seu_segredo_cadastrado",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if not is_valid:
|
|
126
|
+
return {"error": "Assinatura inválida"}, 401
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 🛠️ Desenvolvimento e Testes
|
|
132
|
+
|
|
133
|
+
Para contribuir ou rodar os testes localmente:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Instalar dependências de desenvolvimento com uv:
|
|
137
|
+
uv sync --extra dev
|
|
138
|
+
|
|
139
|
+
# Executar suíte de testes unitários:
|
|
140
|
+
uv run pytest
|
|
141
|
+
|
|
142
|
+
# Executar checagem estrita de tipos com mypy:
|
|
143
|
+
uv run mypy src
|
|
144
|
+
|
|
145
|
+
# Executar linter e formatação com ruff:
|
|
146
|
+
uv run ruff check .
|
|
147
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "coffeemail"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "SDK oficial do CoffeeMail para Python (Síncrono e Assíncrono com tipagem estrita)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "CoffeeMail Team", email = "contato@coffeemail.com.br" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["email", "transactional", "coffeemail", "sdk", "api"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Communications :: Email",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"httpx>=0.27.0",
|
|
29
|
+
"pydantic>=2.6.0",
|
|
30
|
+
"typing-extensions>=4.10.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = [
|
|
35
|
+
"mypy>=1.9.0",
|
|
36
|
+
"pytest>=8.0.0",
|
|
37
|
+
"pytest-asyncio>=0.23.0",
|
|
38
|
+
"pytest-mock>=3.12.0",
|
|
39
|
+
"respx>=0.21.0",
|
|
40
|
+
"ruff>=0.3.0",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://coffeemail.com.br"
|
|
45
|
+
Documentation = "https://docs.coffeemail.com.br"
|
|
46
|
+
Repository = "https://github.com/coffeemail/coffee-mail-sdk-python"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.wheel]
|
|
49
|
+
packages = ["src/coffeemail"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
line-length = 100
|
|
53
|
+
target-version = "py310"
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint]
|
|
56
|
+
select = [
|
|
57
|
+
"E", # pycodestyle errors
|
|
58
|
+
"W", # pycodestyle warnings
|
|
59
|
+
"F", # pyflakes
|
|
60
|
+
"I", # isort
|
|
61
|
+
"B", # flake8-bugbear
|
|
62
|
+
"C4", # flake8-comprehensions
|
|
63
|
+
"UP", # pyupgrade
|
|
64
|
+
"ARG", # flake8-unused-arguments
|
|
65
|
+
"SIM", # flake8-simplify
|
|
66
|
+
"TCH", # flake8-type-checking
|
|
67
|
+
]
|
|
68
|
+
ignore = ["E501"]
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint.isort]
|
|
71
|
+
known-first-party = ["coffeemail"]
|
|
72
|
+
|
|
73
|
+
[tool.mypy]
|
|
74
|
+
plugins = ["pydantic.mypy"]
|
|
75
|
+
python_version = "3.10"
|
|
76
|
+
strict = true
|
|
77
|
+
warn_return_any = true
|
|
78
|
+
warn_unused_configs = true
|
|
79
|
+
disallow_untyped_defs = true
|
|
80
|
+
disallow_incomplete_defs = true
|
|
81
|
+
check_untyped_defs = true
|
|
82
|
+
disallow_untyped_decorators = true
|
|
83
|
+
no_implicit_optional = true
|
|
84
|
+
warn_redundant_casts = true
|
|
85
|
+
warn_unused_ignores = true
|
|
86
|
+
warn_no_return = true
|
|
87
|
+
|
|
88
|
+
[tool.pydantic-mypy]
|
|
89
|
+
init_forbid_extra = true
|
|
90
|
+
init_typed = true
|
|
91
|
+
warn_required_dynamic_aliases = true
|
|
92
|
+
|
|
93
|
+
[tool.pytest.ini_options]
|
|
94
|
+
minversion = "8.0"
|
|
95
|
+
addopts = "-ra -q --strict-markers"
|
|
96
|
+
testpaths = ["tests"]
|
|
97
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Planejamento de Implementação SDD: CoffeeMail Python SDK
|
|
2
|
+
|
|
3
|
+
Este documento estabelece o cronograma de entrega baseado em **Spec-Driven Development (SDD)**, com critérios de aceite, etapas de validação e integração ao **CoffeeMail SDK Workbench**.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🎯 Metodologia de Trabalho
|
|
8
|
+
|
|
9
|
+
Cada fase segue o ciclo rigoroso de SDD:
|
|
10
|
+
1. **Especificação do Contrato**: Validação de parâmetros, tipos Pydantic e assinaturas públicas.
|
|
11
|
+
2. **Testes Unitários / Mock First**: Escrita dos testes com `pytest` e `respx` simulando respostas da API antes da implementação final.
|
|
12
|
+
3. **Implementação da Lógica**: Desenvolvimento com Clean Code e SOLID, sem complexidade acidental.
|
|
13
|
+
4. **Verificação Estrita**: Execução do linter `ruff` e do type checker `mypy --strict`.
|
|
14
|
+
5. **Homologação no Workbench**: Conexão com o `runner-python` na porta 4002 para auditoria na interface visual.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 📋 Fases do Roadmap
|
|
19
|
+
|
|
20
|
+
### **Fase 1: Fundação do Core, Tipos e Clientes Base**
|
|
21
|
+
* **Objetivo**: Estruturar transporte HTTP resiliente (síncrono e assíncrono), envelope de resposta e autenticação.
|
|
22
|
+
* **Tarefas**:
|
|
23
|
+
- Implementar hierarquia de exceções em `coffeemail.core.errors`.
|
|
24
|
+
- Implementar envelope `CoffeeMailResponse[T]` com suporte a desempacotamento de tupla e `.unwrap()`.
|
|
25
|
+
- Implementar camada HTTP com `httpx.Client` e `httpx.AsyncClient` em `coffeemail.core.http`.
|
|
26
|
+
- Implementar cliente principal `CoffeeMail` e `AsyncCoffeeMail`.
|
|
27
|
+
- Implementar introspecção de chave `client.introspect()`.
|
|
28
|
+
* **Critérios de Aceite**:
|
|
29
|
+
- Testes passando com 100% de cobertura para validação de chave nula/inválida.
|
|
30
|
+
- Headers corretos (`User-Agent: coffeemail-python/0.1.0`, `Authorization`, `Accept-Language`).
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
### **Fase 2: Módulo de E-mails Transacionais (`emails`)**
|
|
35
|
+
* **Objetivo**: Implementar o principal recurso do SDK, cobrindo envios simples, em lote, consultas e eventos.
|
|
36
|
+
* **Tarefas**:
|
|
37
|
+
- Modelar DTOs Pydantic v2: `SendEmailPayload`, `SendEmailResponse`, `EmailDetail`, `BatchSendEmailResult`, etc.
|
|
38
|
+
- Implementar normalização de destinatários: aceitar string (`"user@dominio.com"`), dicionário (`{"name": "...", "email": "..."}`) ou listas de ambos.
|
|
39
|
+
- Implementar suporte a anexos: aceitar string base64, bytes brutos ou caminho de arquivo local (`pathlib.Path` / `str`).
|
|
40
|
+
- Implementar métodos: `send`, `send_batch`, `get`, `list`, `cancel`, `get_events`, `list_tags`.
|
|
41
|
+
* **Critérios de Aceite**:
|
|
42
|
+
- Mapeamento correto de `snake_case` para o payload `camelCase` esperado pela API CoffeeMail.
|
|
43
|
+
- Testes unitários com `respx` simulando requisições com múltiplos anexos e parâmetros de agendamento.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### **Fase 3: Módulos de Domínios (`domains`) e Webhooks (`webhooks`)**
|
|
48
|
+
* **Objetivo**: Configuração de remetentes autorizados e recebimento seguro de eventos.
|
|
49
|
+
* **Tarefas**:
|
|
50
|
+
- Implementar métodos de domínios: `list`, `get`, `create`, `verify`, `get_health`, `delete`.
|
|
51
|
+
- Implementar verificação de registros DNS (SPF, DKIM, DMARC, MX).
|
|
52
|
+
- Implementar gestão de endpoints de Webhook: `list`, `get`, `create`, `update`, `toggle`, `rotate_secret`, `test`, `list_deliveries`.
|
|
53
|
+
- Implementar utilitário criptográfico `webhooks.verify_signature` com verificação de timestamp e tolerância a drift de relógio.
|
|
54
|
+
* **Critérios de Aceite**:
|
|
55
|
+
- Testes unitários comprovando rejeição de assinaturas HMAC inválidas ou com timestamp expirado.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### **Fase 4: Modelos (`templates`) e Pré-visualização**
|
|
60
|
+
* **Objetivo**: Gestão de templates HTML e renderização de prévias.
|
|
61
|
+
* **Tarefas**:
|
|
62
|
+
- Implementar métodos: `list`, `get`, `create`, `update`, `delete`.
|
|
63
|
+
- Implementar `preview` suportando tanto ID de modelo existente quanto código HTML bruto com variáveis de substituição.
|
|
64
|
+
- Implementar `test_send` para envio de prova.
|
|
65
|
+
* **Critérios de Aceite**:
|
|
66
|
+
- Retorno consistente com envelopamento `{ templates: [...] }`.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### **Fase 5: Audiências (`audiences`), Contatos (`contacts`) e Campanhas (`broadcasts`)**
|
|
71
|
+
* **Objetivo**: Gerenciamento de listas de e-mail e envios em massa.
|
|
72
|
+
* **Tarefas**:
|
|
73
|
+
- Implementar gestão de audiências e contatos aninhados (`audiences.contacts`).
|
|
74
|
+
- Adicionar atalhos ergonômicos no topo de `audiences` (`list_contacts`, `create_contact`, `bulk_add_contacts`).
|
|
75
|
+
- Implementar módulo de campanhas `broadcasts` (`list`, `get`, `create`, `send`, `cancel`, `delete`).
|
|
76
|
+
* **Critérios de Aceite**:
|
|
77
|
+
- Tipagem completa de atributos personalizados de contatos (`attributes: dict[str, Any]`).
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### **Fase 6: Supressões (`suppressions`) e Estatísticas (`stats`)**
|
|
82
|
+
* **Objetivo**: Controle de reputação de remetente e visualização analítica.
|
|
83
|
+
* **Tarefas**:
|
|
84
|
+
- Implementar `suppressions` para consulta e exclusão de bounces/unsubscribes.
|
|
85
|
+
- Implementar `stats` para recuperação de taxas de abertura, entrega e rejeição por período.
|
|
86
|
+
* **Critérios de Aceite**:
|
|
87
|
+
- Validação de formatação de datas ISO 8601 nos filtros de estatísticas.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### **Fase 7: Integração com o Workbench e Runner Python**
|
|
92
|
+
* **Objetivo**: Criar o backend `coffee-mail-sdk-workbench/backends/runner-python` consumindo o pacote local.
|
|
93
|
+
* **Tarefas**:
|
|
94
|
+
- Criar mini-backend em FastAPI ou Flask na porta 4002 implementando a Runner Specification API.
|
|
95
|
+
- Habilitar o seletor "Python Runner" no front-end web do Workbench.
|
|
96
|
+
- Executar bateria completa de testes de ponta a ponta na interface visual.
|
|
97
|
+
* **Critérios de Aceite**:
|
|
98
|
+
- 100% dos botões e formulários do Workbench funcionando igualmente no Runner Node e no Runner Python.
|