Official Python SDK for the Lettermint sending and team APIs.
pip install lettermintfromlettermintimportLettermintemail=Lettermint.email("your-sending-token")
response= (
email
.from_("sender@example.com")
.to("recipient@example.com")
.subject("Hello from Python!")
.html("<h1>Welcome!</h1>")
.text("Welcome!")
.send()
)
print(response["message_id"])fromlettermintimportAsyncLettermintemail=AsyncLettermint.email("your-sending-token")
response=await (
email
.from_("sender@example.com")
.to("recipient@example.com")
.subject("Hello from Python!")
.html("<h1>Welcome!</h1>")
.send()
)
print(response["message_id"])The legacy constructor still works for sending-only usage:
client=Lettermint(api_token="your-sending-token")
client.email.from_("sender@example.com").to("recipient@example.com").subject("Hello").send()client.email.from_("sender@example.com").to(
"recipient1@example.com",
"recipient2@example.com"
).subject("Hello").send()client.email.from_("sender@example.com").to("recipient@example.com").cc(
"cc1@example.com",
"cc2@example.com"
).bcc("bcc@example.com").subject("Hello").send()client.email.from_("sender@example.com").to("recipient@example.com").reply_to(
"reply@example.com"
).subject("Hello").send()client.email.from_("John Doe <john@example.com>").to(
"Jane Doe <jane@example.com>"
).subject("Hello").send()importbase64# Read and encode your filewithopen("document.pdf", "rb") asf:
content=base64.b64encode(f.read()).decode()
# Regular attachmentclient.email.from_("sender@example.com").to("recipient@example.com").subject(
"Your Document"
).attach("document.pdf", content).send()
# Inline attachment (for embedding in HTML)client.email.from_("sender@example.com").to("recipient@example.com").subject(
"Welcome"
).html('<img src="cid:logo@example.com">').attach(
"logo.png", logo_content, "logo@example.com"
).send()client.email.from_("sender@example.com").to("recipient@example.com").subject(
"Hello"
).headers({"X-Custom-Header": "value"}).send()client.email.from_("sender@example.com").to("recipient@example.com").subject(
"Hello"
).metadata({"campaign_id": "123", "user_id": "456"}).tag("welcome-campaign").send()client.email.from_("sender@example.com").to("recipient@example.com").subject(
"Hello"
).route("my-route").send()Prevent duplicate sends when retrying failed requests:
client.email.from_("sender@example.com").to("recipient@example.com").subject(
"Hello"
).idempotency_key("unique-request-id").send()email=Lettermint.email("your-sending-token")
response=email.send_batch([
{
"from": "sender@example.com",
"to": ["recipient@example.com"],
"subject": "Hello from Python!",
"text": "This is a batch email.",
}
])Both sync and async sending clients support ping():
email.ping()
awaitAsyncLettermint.email("your-sending-token").ping()Use a team API token with Lettermint.api(...). API tokens authenticate with Authorization: Bearer ... and are separate from project sending tokens.
fromlettermintimportLettermintapi=Lettermint.api("your-api-token")
domains=api.domains.list({"page[size]": "10"})
team=api.team.retrieve()
message_html=api.messages.html("message-id")
pong=api.ping()The async Team API client is available through AsyncLettermint.api(...):
fromlettermintimportAsyncLettermintapi=AsyncLettermint.api("your-api-token")
domains=awaitapi.domains.list({"page[size]": "10"})
message_html=awaitapi.messages.html("message-id")Endpoint groups are available as domains, messages, projects, routes, stats, suppressions, team, and webhooks.
Verify webhook signatures to ensure authenticity:
fromlettermintimportWebhook# Create a webhook verifierwebhook=Webhook(secret="your-webhook-secret")
# Verify using headers (recommended)payload=webhook.verify_headers(request.headers, request.body)
# Or verify using the signature directlypayload=webhook.verify(
payload=request.body,
signature=request.headers["X-Lettermint-Signature"],
)
print(payload["event"])For one-off verification:
fromlettermintimportWebhookpayload=Webhook.verify_signature(
payload=request.body,
signature=request.headers["X-Lettermint-Signature"],
secret="your-webhook-secret",
)Adjust the timestamp tolerance (default: 300 seconds):
webhook=Webhook(secret="your-webhook-secret", tolerance=600)fromlettermintimportLettermintfromlettermint.exceptionsimport (
ValidationError,
ClientError,
HttpRequestError,
TimeoutError,
)
client=Lettermint(api_token="your-sending-token")
try:
response=client.email.from_("sender@example.com").to("recipient@example.com").subject(
"Hello"
).send()
exceptValidationErrorase:
# 422 errors (e.g., daily limit exceeded)print(f"Validation error: {e.error_type}")
print(f"Response: {e.response_body}")
exceptClientErrorase:
# 400 errorsprint(f"Client error: {e}")
exceptTimeoutErrorase:
# Request timeoutprint(f"Timeout: {e}")
exceptHttpRequestErrorase:
# Other HTTP errorsprint(f"HTTP error {e.status_code}: {e}")fromlettermintimportWebhookfromlettermint.exceptionsimport (
InvalidSignatureError,
TimestampToleranceError,
JsonDecodeError,
WebhookVerificationError,
)
try:
payload=webhook.verify_headers(headers, body)
exceptInvalidSignatureError:
print("Invalid signature - request may be forged")
exceptTimestampToleranceError:
print("Timestamp too old - possible replay attack")
exceptJsonDecodeError:
print("Invalid JSON in payload")
exceptWebhookVerificationErrorase:
print(f"Verification failed: {e}")client=Lettermint(
api_token="your-sending-token",
base_url="https://custom.api.com/v1",
)client=Lettermint(
api_token="your-sending-token",
timeout=60.0, # 60 seconds
)Both sync and async clients support context managers for proper resource cleanup:
# SyncwithLettermint(api_token="your-sending-token") asclient:
client.email.from_("sender@example.com").to("recipient@example.com").send()
# AsyncasyncwithAsyncLettermint(api_token="your-sending-token") asclient:
awaitclient.email.from_("sender@example.com").to("recipient@example.com").send()This SDK is fully typed with py.typed marker. You'll get full autocomplete and type checking in your IDE.
- Python 3.9+
- httpx
MIT