smtpfast 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.
@@ -0,0 +1,40 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python: ["3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v7
16
+ - uses: actions/setup-python@v6
17
+ with:
18
+ python-version: ${{ matrix.python }}
19
+ - run: pip install -e '.[dev]'
20
+ # The generated code must be exactly what the spec in the repo produces.
21
+ - name: Generated code is up to date
22
+ run: python scripts/generate.py && git diff --exit-code
23
+ - run: ruff check src tests scripts
24
+ - run: mypy
25
+ - run: pytest -q
26
+
27
+ # Read-only calls to the real API, checked against the spec. Needs a key
28
+ # with email:read and domain:read in the SMTPFAST_API_KEY secret.
29
+ live:
30
+ if: github.event_name == 'push'
31
+ runs-on: ubuntu-latest
32
+ steps:
33
+ - uses: actions/checkout@v7
34
+ - uses: actions/setup-python@v6
35
+ with:
36
+ python-version: "3.13"
37
+ - run: pip install -e '.[dev]'
38
+ - run: pytest -q tests/test_live.py
39
+ env:
40
+ SMTPFAST_API_KEY: ${{ secrets.SMTPFAST_API_KEY }}
@@ -0,0 +1,50 @@
1
+ name: Regenerate from the API spec
2
+
3
+ # Pulls the published OpenAPI document, regenerates the SDK and opens a pull
4
+ # request when anything changed. Runs daily, on demand, and when the API repo
5
+ # sends a spec-changed dispatch.
6
+ on:
7
+ schedule:
8
+ - cron: "23 5 * * *"
9
+ workflow_dispatch:
10
+ repository_dispatch:
11
+ types: [spec-changed]
12
+
13
+ permissions:
14
+ contents: write
15
+ pull-requests: write
16
+
17
+ jobs:
18
+ regenerate:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v7
22
+ - uses: actions/setup-python@v6
23
+ with:
24
+ python-version: "3.13"
25
+ - run: pip install -e '.[dev]'
26
+ - run: python scripts/pull_spec.py
27
+ # Until the API publishes x-sdk-group/x-sdk-method, there is nothing to regenerate from.
28
+ - name: Spec has SDK names
29
+ id: names
30
+ run: |
31
+ if grep -q '"x-sdk-method"' spec/openapi.json; then echo "ok=true" >> "$GITHUB_OUTPUT"; else echo "::notice::The published spec has no x-sdk names yet; skipping."; echo "ok=false" >> "$GITHUB_OUTPUT"; fi
32
+ - run: python scripts/generate.py
33
+ if: steps.names.outputs.ok == 'true'
34
+ - run: mypy
35
+ if: steps.names.outputs.ok == 'true'
36
+ - run: pytest -q
37
+ if: steps.names.outputs.ok == 'true'
38
+ # A pull request opened with the default GITHUB_TOKEN does not start the
39
+ # CI workflow. Set SDK_BOT_TOKEN (a GitHub App or fine-grained token with
40
+ # contents and pull-requests write) so it does.
41
+ - uses: peter-evans/create-pull-request@v7
42
+ if: steps.names.outputs.ok == 'true'
43
+ with:
44
+ token: ${{ secrets.SDK_BOT_TOKEN || github.token }}
45
+ branch: spec-sync
46
+ title: "Sync with the API spec"
47
+ commit-message: "Sync with the API spec"
48
+ body: |
49
+ The published OpenAPI document changed. This regenerates `src/smtpfast/types.py` and `src/smtpfast/_resources.py` from it.
50
+ Review the diff of `spec/openapi.json` for what changed in the API.
@@ -0,0 +1,28 @@
1
+ name: Release
2
+
3
+ # Publish to PyPI when a GitHub release is published, with PyPI trusted
4
+ # publishing (no token). The tag is the version, for example v0.2.0; it must
5
+ # match pyproject.toml.
6
+ on:
7
+ release:
8
+ types: [published]
9
+
10
+ permissions:
11
+ contents: read
12
+ id-token: write
13
+
14
+ jobs:
15
+ publish:
16
+ runs-on: ubuntu-latest
17
+ environment: pypi
18
+ steps:
19
+ - uses: actions/checkout@v7
20
+ - uses: actions/setup-python@v6
21
+ with:
22
+ python-version: "3.13"
23
+ - name: Tag matches pyproject.toml
24
+ run: test "v$(python -c 'import tomllib;print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')" = "${GITHUB_REF_NAME}"
25
+ - run: pip install -e '.[dev]' build
26
+ - run: pytest -q
27
+ - run: python -m build
28
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ dist/
5
+ .pytest_cache/
6
+ .mypy_cache/
7
+ .ruff_cache/
8
+ .env
smtpfast-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SMTPfast
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,131 @@
1
+ Metadata-Version: 2.5
2
+ Name: smtpfast
3
+ Version: 0.1.0
4
+ Summary: The official Python SDK for the SMTPfast API.
5
+ Project-URL: Homepage, https://smtpfa.st
6
+ Project-URL: Documentation, https://smtpfa.st/docs
7
+ Project-URL: Source, https://github.com/smtpfast/smtpfast-python
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: api,email,sdk,smtpfast,transactional-email
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Communications :: Email
13
+ Classifier: Typing :: Typed
14
+ Requires-Python: >=3.10
15
+ Requires-Dist: httpx<1,>=0.27
16
+ Requires-Dist: typing-extensions>=4.7
17
+ Provides-Extra: dev
18
+ Requires-Dist: jsonschema>=4.22; extra == 'dev'
19
+ Requires-Dist: mypy>=1.10; extra == 'dev'
20
+ Requires-Dist: pytest>=8; extra == 'dev'
21
+ Requires-Dist: ruff>=0.5; extra == 'dev'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # SMTPfast Python SDK
25
+
26
+ The official Python SDK for the [SMTPfast](https://smtpfa.st) API. It covers every endpoint in the API, sync and async, with types for every request and response. It needs Python 3.10+ and depends only on `httpx`.
27
+
28
+ ```bash
29
+ pip install smtpfast
30
+ ```
31
+
32
+ ## Send an email
33
+
34
+ ```python
35
+ from smtpfast import SMTPfast
36
+
37
+ client = SMTPfast() # reads SMTPFAST_API_KEY
38
+
39
+ email = client.emails.send({
40
+ "from": "Acme <hello@yourdomain.com>",
41
+ "to": ["customer@example.org"],
42
+ "subject": "Your receipt",
43
+ "html": "<p>Thanks for your order.</p>",
44
+ "category": "transactional",
45
+ })
46
+ print(email["id"])
47
+ ```
48
+
49
+ Email is marketing unless you set `category` to `transactional`. Marketing email gets a one-click unsubscribe header and is not sent to people who unsubscribed. Set `transactional` on receipts, password resets and account alerts so a marketing opt-out does not block them.
50
+
51
+ Requests and responses are plain dicts, typed with `TypedDict`s from `smtpfast.types`, so your editor and type checker know every field. A new field or value from the API never breaks your code, because the SDK does not validate responses at runtime.
52
+
53
+ Every API operation is a method, named after its path: `client.domains.verify(id)`, `client.contacts.segments.add(contact_id, segment_id)`, `client.inboxes.threads.emails.reply(inbox_id, thread_id, email_id, params)`. Path parameters come first, then the request body, and query parameters are keyword arguments: `client.emails.list(limit=20, status="bounced")`.
54
+
55
+ ## Async
56
+
57
+ ```python
58
+ from smtpfast import AsyncSMTPfast
59
+
60
+ async with AsyncSMTPfast() as client:
61
+ await client.emails.send({...})
62
+ ```
63
+
64
+ ## Options
65
+
66
+ ```python
67
+ client = SMTPfast(
68
+ api_key="sf_...", # default: SMTPFAST_API_KEY
69
+ base_url="https://smtpfa.st/api",
70
+ max_retries=2, # after 429 (with Retry-After), 408, 5xx and dropped connections
71
+ timeout=60.0, # per attempt, in seconds
72
+ )
73
+
74
+ client.emails.send(params, idempotency_key="order-1234", options={"timeout": 10})
75
+ ```
76
+
77
+ ## Retries and idempotency
78
+
79
+ Reads, `PUT` and `DELETE` are retried after a 5xx answer or a dropped connection. A send, reply or forward is retried with the same `Idempotency-Key`, so the API returns the first answer instead of sending twice. When you do not give a key, the SDK makes one per call. Other `POST` and `PATCH` calls are retried only after a per-second rate limit, which the API rejects before doing any work.
80
+
81
+ ## Errors
82
+
83
+ Every error is an `SMTPfastError`. An answer outside 2xx raises an `APIError` with `status`, `body` and `headers`, as one of `BadRequestError`, `AuthenticationError`, `PermissionDeniedError`, `NotFoundError`, `ConflictError`, `UnprocessableEntityError`, `RateLimitError` or `InternalServerError`. No answer at all raises `APIConnectionError` or `APITimeoutError`.
84
+
85
+ ```python
86
+ import smtpfast
87
+
88
+ try:
89
+ client.emails.get("em_missing")
90
+ except smtpfast.NotFoundError as err:
91
+ print(err.status, err.body)
92
+ ```
93
+
94
+ ## Pagination
95
+
96
+ Lists that page with a cursor also have a `..._all` method that fetches the next pages as you iterate:
97
+
98
+ ```python
99
+ for email in client.emails.list_all(limit=100):
100
+ print(email["id"], email["status"])
101
+ ```
102
+
103
+ ## Webhooks
104
+
105
+ ```python
106
+ from smtpfast import webhooks
107
+
108
+ # The raw body, before any JSON parsing.
109
+ event = webhooks.parse(request.body, request.headers, secret=os.environ["SMTPFAST_WEBHOOK_SECRET"])
110
+ if event["type"] == "email.bounced":
111
+ ...
112
+ ```
113
+
114
+ `webhooks.verify_signature(payload, signature, secret)` returns a bool if you only need the check.
115
+
116
+ ## How this SDK is made
117
+
118
+ The SDK is generated from the [OpenAPI document](https://smtpfa.st/api/v1/openapi.json), so it follows the API without hand edits. `scripts/generate.py` (standard library only) writes `src/smtpfast/types.py` and `src/smtpfast/_resources.py`, naming every method from the `x-sdk-group` and `x-sdk-method` the spec gives each operation; the SDKs for other languages use the same names. `_core.py`, `_errors.py` and `webhooks.py` are written by hand.
119
+
120
+ ```bash
121
+ python scripts/pull_spec.py # download the published spec into spec/openapi.json
122
+ python scripts/generate.py # regenerate types.py and _resources.py
123
+ pytest -q # unit tests, including one call per operation
124
+ SMTPFAST_API_KEY=sf_... pytest tests/test_live.py # read-only calls, checked against the spec
125
+ ```
126
+
127
+ A daily workflow pulls the spec and opens a pull request when it changed.
128
+
129
+ ## License
130
+
131
+ MIT
@@ -0,0 +1,108 @@
1
+ # SMTPfast Python SDK
2
+
3
+ The official Python SDK for the [SMTPfast](https://smtpfa.st) API. It covers every endpoint in the API, sync and async, with types for every request and response. It needs Python 3.10+ and depends only on `httpx`.
4
+
5
+ ```bash
6
+ pip install smtpfast
7
+ ```
8
+
9
+ ## Send an email
10
+
11
+ ```python
12
+ from smtpfast import SMTPfast
13
+
14
+ client = SMTPfast() # reads SMTPFAST_API_KEY
15
+
16
+ email = client.emails.send({
17
+ "from": "Acme <hello@yourdomain.com>",
18
+ "to": ["customer@example.org"],
19
+ "subject": "Your receipt",
20
+ "html": "<p>Thanks for your order.</p>",
21
+ "category": "transactional",
22
+ })
23
+ print(email["id"])
24
+ ```
25
+
26
+ Email is marketing unless you set `category` to `transactional`. Marketing email gets a one-click unsubscribe header and is not sent to people who unsubscribed. Set `transactional` on receipts, password resets and account alerts so a marketing opt-out does not block them.
27
+
28
+ Requests and responses are plain dicts, typed with `TypedDict`s from `smtpfast.types`, so your editor and type checker know every field. A new field or value from the API never breaks your code, because the SDK does not validate responses at runtime.
29
+
30
+ Every API operation is a method, named after its path: `client.domains.verify(id)`, `client.contacts.segments.add(contact_id, segment_id)`, `client.inboxes.threads.emails.reply(inbox_id, thread_id, email_id, params)`. Path parameters come first, then the request body, and query parameters are keyword arguments: `client.emails.list(limit=20, status="bounced")`.
31
+
32
+ ## Async
33
+
34
+ ```python
35
+ from smtpfast import AsyncSMTPfast
36
+
37
+ async with AsyncSMTPfast() as client:
38
+ await client.emails.send({...})
39
+ ```
40
+
41
+ ## Options
42
+
43
+ ```python
44
+ client = SMTPfast(
45
+ api_key="sf_...", # default: SMTPFAST_API_KEY
46
+ base_url="https://smtpfa.st/api",
47
+ max_retries=2, # after 429 (with Retry-After), 408, 5xx and dropped connections
48
+ timeout=60.0, # per attempt, in seconds
49
+ )
50
+
51
+ client.emails.send(params, idempotency_key="order-1234", options={"timeout": 10})
52
+ ```
53
+
54
+ ## Retries and idempotency
55
+
56
+ Reads, `PUT` and `DELETE` are retried after a 5xx answer or a dropped connection. A send, reply or forward is retried with the same `Idempotency-Key`, so the API returns the first answer instead of sending twice. When you do not give a key, the SDK makes one per call. Other `POST` and `PATCH` calls are retried only after a per-second rate limit, which the API rejects before doing any work.
57
+
58
+ ## Errors
59
+
60
+ Every error is an `SMTPfastError`. An answer outside 2xx raises an `APIError` with `status`, `body` and `headers`, as one of `BadRequestError`, `AuthenticationError`, `PermissionDeniedError`, `NotFoundError`, `ConflictError`, `UnprocessableEntityError`, `RateLimitError` or `InternalServerError`. No answer at all raises `APIConnectionError` or `APITimeoutError`.
61
+
62
+ ```python
63
+ import smtpfast
64
+
65
+ try:
66
+ client.emails.get("em_missing")
67
+ except smtpfast.NotFoundError as err:
68
+ print(err.status, err.body)
69
+ ```
70
+
71
+ ## Pagination
72
+
73
+ Lists that page with a cursor also have a `..._all` method that fetches the next pages as you iterate:
74
+
75
+ ```python
76
+ for email in client.emails.list_all(limit=100):
77
+ print(email["id"], email["status"])
78
+ ```
79
+
80
+ ## Webhooks
81
+
82
+ ```python
83
+ from smtpfast import webhooks
84
+
85
+ # The raw body, before any JSON parsing.
86
+ event = webhooks.parse(request.body, request.headers, secret=os.environ["SMTPFAST_WEBHOOK_SECRET"])
87
+ if event["type"] == "email.bounced":
88
+ ...
89
+ ```
90
+
91
+ `webhooks.verify_signature(payload, signature, secret)` returns a bool if you only need the check.
92
+
93
+ ## How this SDK is made
94
+
95
+ The SDK is generated from the [OpenAPI document](https://smtpfa.st/api/v1/openapi.json), so it follows the API without hand edits. `scripts/generate.py` (standard library only) writes `src/smtpfast/types.py` and `src/smtpfast/_resources.py`, naming every method from the `x-sdk-group` and `x-sdk-method` the spec gives each operation; the SDKs for other languages use the same names. `_core.py`, `_errors.py` and `webhooks.py` are written by hand.
96
+
97
+ ```bash
98
+ python scripts/pull_spec.py # download the published spec into spec/openapi.json
99
+ python scripts/generate.py # regenerate types.py and _resources.py
100
+ pytest -q # unit tests, including one call per operation
101
+ SMTPFAST_API_KEY=sf_... pytest tests/test_live.py # read-only calls, checked against the spec
102
+ ```
103
+
104
+ A daily workflow pulls the spec and opens a pull request when it changed.
105
+
106
+ ## License
107
+
108
+ MIT
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.24"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "smtpfast"
7
+ version = "0.1.0"
8
+ description = "The official Python SDK for the SMTPfast API."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ dependencies = ["httpx>=0.27,<1", "typing_extensions>=4.7"]
14
+ keywords = ["smtpfast", "email", "transactional-email", "api", "sdk"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Typing :: Typed",
18
+ "Topic :: Communications :: Email",
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://smtpfa.st"
23
+ Documentation = "https://smtpfa.st/docs"
24
+ Source = "https://github.com/smtpfast/smtpfast-python"
25
+
26
+ [project.optional-dependencies]
27
+ dev = ["pytest>=8", "mypy>=1.10", "jsonschema>=4.22", "ruff>=0.5"]
28
+
29
+ [tool.hatch.build.targets.wheel]
30
+ packages = ["src/smtpfast"]
31
+
32
+ [tool.pytest.ini_options]
33
+ testpaths = ["tests"]
34
+
35
+ [tool.mypy]
36
+ strict = true
37
+ files = ["src/smtpfast"]
38
+
39
+ [[tool.mypy.overrides]]
40
+ module = ["smtpfast._resources", "smtpfast._core"]
41
+ disallow_untyped_defs = false
42
+ disallow_untyped_calls = false
43
+ disallow_incomplete_defs = false
@@ -0,0 +1,5 @@
1
+ line-length = 140
2
+ extend-exclude = ["src/smtpfast/types.py", "src/smtpfast/_resources.py"]
3
+
4
+ [lint.per-file-ignores]
5
+ "tests/*" = ["E731"]