mailhive
The official Python SDK for Mailhive Send. It supports Python 3.9+, with sync and async clients and a Django email backend.
pip install mailhive
Send an email
from mailhive import Mailhive
client = Mailhive() # reads MAILHIVE_API_KEY
email = client.emails.send({
"from": "Acme <hello@acme.com>",
"to": "ada@example.com",
"subject": "Your receipt",
"html": "<p>Thanks for your order.</p>",
})
print(email["id"])
The keys match the API reference exactly: cc, bcc, reply_to, headers, tags, template_id, variables and attachments. You can also pass keyword arguments, with from_ standing in for from:
client.emails.send(from_="hello@acme.com", to="ada@example.com", subject="Hi", text="Hello")
Attachment content can be bytes, which are encoded for you, or base64 text.
client.emails.send_batch([email1, email2]) # up to 100; all accepted or none
client.emails.get(email_id) # status: "delivered", "bounced", …
Async
from mailhive import AsyncMailhive
async with AsyncMailhive() as client:
await client.emails.send({...})
Retries and idempotency
Every send carries an Idempotency-Key. Retries reuse it, so a retry never sends twice.
- Retried (up to 2 times, then configurable): network errors, timeouts, 5xx responses, and
429 rate_limitedafter theRetry-Afterdelay. - Not retried: a used-up allowance (
monthly_quota_reached,daily_cap_reached) and invalid requests.
To stay safe across restarts, pass your own key:
client.emails.send(email, idempotency_key=f"order-{order.id}-receipt")
Errors
from mailhive import RateLimitError, ValidationError
try:
client.emails.send(email)
except ValidationError as error:
print(error.code, error.message, error.details, error.request_id)
except RateLimitError as error:
if error.code == "monthly_quota_reached":
...
| Exception | Status |
|---|---|
AuthenticationError |
401 |
BillingError |
402 |
PermissionDeniedError |
403 |
NotFoundError |
404 |
ConflictError |
409 (e.g. idempotency_conflict) |
ValidationError |
422 |
RateLimitError |
429 (retry_after in seconds) |
APIError |
other statuses, and the base class of all of the above |
APIConnectionError |
no response |
All of them subclass MailhiveError.
Webhooks
Pass the raw body, exactly as received:
from mailhive import WebhookVerificationError, verify_webhook
# Django
def mailhive_webhook(request):
try:
event = verify_webhook(request.body, request.headers.get("Mailhive-Signature"), settings.MAILHIVE_WEBHOOK_SECRET)
except WebhookVerificationError:
return HttpResponse(status=400)
...
# Flask: verify_webhook(request.get_data(), request.headers.get("Mailhive-Signature"), secret)
# FastAPI: verify_webhook(await request.body(), request.headers.get("mailhive-signature"), secret)
Django
# settings.py, Django 6.1 and later
MAILERS = {
"default": {
"BACKEND": "mailhive.django.EmailBackend",
"OPTIONS": {"api_key": os.environ["MAILHIVE_API_KEY"]},
},
}
# settings.py, earlier versions
EMAIL_BACKEND = "mailhive.django.EmailBackend"
MAILHIVE_API_KEY = os.environ["MAILHIVE_API_KEY"]
send_mail, EmailMessage and EmailMultiAlternatives all send through Mailhive, including HTML alternatives, attachments, cc, bcc, Reply-To and extra headers. You can also set message.mailhive_tags or message.mailhive_idempotency_key on a message.
Options
Mailhive(
api_key="mhs_…", # default: MAILHIVE_API_KEY
base_url="https://api-beta.mailhive.africa/v1", # default: MAILHIVE_BASE_URL, then production
timeout=30.0,
max_retries=2,
)
Test keys (mhs_test_…) work unchanged: delivery is simulated and nothing is billed. More about test keys.
Metadata
Release files for mailhive 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mailhive-0.1.0.tar.gz | 13.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mailhive-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 26.9 kB
Release files / mailhive-0.1.0.tar.gz
| Download URL | mailhive-0.1.0.tar.gz |
|---|---|
| Size | 13.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
958fc37dcfa6d9a4b30ba1b764a7c1165099a1b0d6ee349e21efcc567dc9d42b
|
|
BLAKE2b-256 checksum How to use checksums |
4baef446cac5b0480bc57ab4f2090a9285d4933e1ee333701e3f737aab9cbd59
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / mailhive-0.1.0-py3-none-any.whl
| Download URL | mailhive-0.1.0-py3-none-any.whl |
|---|---|
| Size | 13.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8c65d1466af071dde0c4c3661607cd49b47b2f5f9a0c30e545c7228a63468d55
|
|
BLAKE2b-256 checksum How to use checksums |
19491cd9f05ae704435afffb6c81cb54cc59c4088081435b296950368b24bf2b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency log