senditdaddy
Send email from a domain your SendItDaddy workspace has already connected.
No dependencies — the standard library does the HTTP.
pip install senditdaddy
Send something
from senditdaddy import SendItDaddy
client = SendItDaddy(api_key="sid_sk_...")
email = client.emails.send(
from_="Acme <hello@yourdomain.com>",
to="someone@example.com",
subject="Your receipt",
html="<p>Thanks for your order.</p>",
)
print(email["id"]) # msg__k3nR8x...
from_ has the trailing underscore because from is a Python keyword.
sender= works too, and so does a plain dict, which is what makes a payload
copied out of the HTTP docs run unchanged:
client.emails.send({
"from": "hello@yourdomain.com",
"to": ["a@example.com", "b@example.com"],
"cc": "boss@example.com",
"bcc": [],
"reply_to": "support@yourdomain.com",
"subject": "Hello",
"html": "<p>Hi there</p>",
"text": "Hi there",
})
to, cc and bcc each take one address or a list. Supply html, text, or
both — with only html, a plain-text alternative is generated for you, because
a message with no text/plain part scores worse with spam filters.
Get the key from Dashboard → API keys. It is shown once, at creation; there is no endpoint that reveals it again, because it is stored as a hash. Prefer the environment over a literal:
client = SendItDaddy() # reads SENDITDADDY_API_KEY
Which addresses can I send from?
Whatever the workspace has connected and verified. Ask, rather than guessing — an unconnected From address is a 403 that is otherwise hard to diagnose:
for address in client.addresses()["addresses"]:
print(address["address"], address["can_send"], address["quota"]["remaining"])
Each address includes 500 sends per UTC day.
Read back what you sent
client.emails.get("msg__k3nR8x...") # one, with bodies
client.emails.list(direction="outbound") # a page, without bodies
client.emails.list(status="failed", page=2)
List responses are {"results": [...], "count": N, "page": 1, "pages": 3, "has_next": bool, "has_previous": bool}. Bodies are left out of the list
deliberately: they are encrypted at rest, and decrypting a page of them for
content the list does not show is work for nothing.
When it goes wrong
Every failure is a subclass of APIError, and error.code is the stable thing
to branch on — it does not change when someone improves the wording.
from senditdaddy import (
AuthenticationError, # 401 key missing, revoked, or expired
PermissionDeniedError, # 403 not an address this key may send as
NotFoundError, # 404 no such email
ConflictError, # 409 the domain has not verified yet
ValidationError, # 422 something in the request was not acceptable
RateLimitError, # 429 too many requests, or out of daily allowance
ServerError, # 5xx including 502 when the upstream rejected it
TransportError, # never got an answer at all
)
try:
client.emails.send(from_="hello@yourdomain.com", to="a@example.com",
subject="Hi", text="Hi")
except ValidationError as error:
print(error.errors) # {"text": ["Give the message a body."]}
except RateLimitError as error:
print(error.code) # rate_limit_exceeded | daily_send_limit_reached
print(error.retry_after) # seconds, when the API gave one
except PermissionDeniedError as error:
print(error.message) # "This API key is not allowed to send as ..."
Nothing is retried automatically. A send is not idempotent: a request that
timed out may well have gone out, and a library that quietly repeats it
delivers the message twice to somebody who only wanted it once. TransportError
is deliberately not an APIError for that reason — it means the outcome is
unknown, which is a different decision from a clean rejection.
Configuration
| Argument | Default | |
|---|---|---|
api_key |
$SENDITDADDY_API_KEY |
required |
base_url |
$SENDITDADDY_BASE_URL, else https://api.senditdaddy.com |
point at your own deployment |
timeout |
30.0 |
seconds |
transport |
UrllibTransport |
see below |
user_agent |
senditdaddy-python/<version> |
Bringing your own HTTP client
Applications running on requests or httpx usually have retries, proxies,
pooling and tracing configured on it. Pass an adapter and every call inherits
all of that instead of quietly going around it. It needs one method:
import requests
from senditdaddy import Response, SendItDaddy
class RequestsTransport:
def request(self, method, url, *, headers, body=None, timeout=None):
reply = requests.request(method, url, headers=headers, data=body,
timeout=timeout)
return Response(reply.status_code, dict(reply.headers), reply.content)
client = SendItDaddy(api_key="sid_sk_...", transport=RequestsTransport())
A 4xx must be returned, not raised — the body carries the reason, and the client turns it into the right typed error.
The HTTP API underneath
POST /api/emails send 201
GET /api/emails list 200 ?direction= ?status= ?from= ?page= ?page_size=
GET /api/emails/{id} one, with bodies 200
GET /api/addresses what you may send as
Authenticate with Authorization: Bearer sid_sk_... on every request.
curl https://api.senditdaddy.com/api/emails \
-H "Authorization: Bearer $SENDITDADDY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"from":"hello@yourdomain.com","to":"someone@example.com",
"subject":"Hello","html":"<p>Hi there</p>"}'
Tests
No test dependencies either — the client is exercised against a transport that records calls and returns canned responses.
cd sdk/python
python -m unittest discover -s tests
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file senditdaddy-0.1.0.tar.gz.
File metadata
- Download URL: senditdaddy-0.1.0.tar.gz
- Upload date:
- Size: 12.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cb2d8921125542079d47c1f82b03c7719cb31e88708ac5d9bbf3052098b4034b
|
|
| MD5 |
164bb3328a920cb57ce3f9e67e1c4b46
|
|
| BLAKE2b-256 |
55fde98326bd23129ce5523cb4ab8c4ae1c55017ca36ccf71b0d9947701405e1
|
File details
Details for the file senditdaddy-0.1.0-py3-none-any.whl.
File metadata
- Download URL: senditdaddy-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d14f22bd6cefd5aea406071b76dd4d682f9d49cac81a71e6e0729b9aeec0384a
|
|
| MD5 |
50bb8326a18fe9483c23ff40d8c239bf
|
|
| BLAKE2b-256 |
47da4794ff0348c8b5a355f1bade490fe0fdfad4dc04a8c74b133cfe13302e79
|