Skip to main content

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

senditdaddy-0.1.0.tar.gz (12.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

senditdaddy-0.1.0-py3-none-any.whl (12.7 kB view details)

Uploaded Python 3

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

Hashes for senditdaddy-0.1.0.tar.gz
Algorithm Hash digest
SHA256 cb2d8921125542079d47c1f82b03c7719cb31e88708ac5d9bbf3052098b4034b
MD5 164bb3328a920cb57ce3f9e67e1c4b46
BLAKE2b-256 55fde98326bd23129ce5523cb4ab8c4ae1c55017ca36ccf71b0d9947701405e1

See more details on using hashes here.

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

Hashes for senditdaddy-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d14f22bd6cefd5aea406071b76dd4d682f9d49cac81a71e6e0729b9aeec0384a
MD5 50bb8326a18fe9483c23ff40d8c239bf
BLAKE2b-256 47da4794ff0348c8b5a355f1bade490fe0fdfad4dc04a8c74b133cfe13302e79

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page