PyPI Version PyPI Downloads Python Version GitHub Tests License Join our Discord server
Official Python SDK for the Lettermint sending and team APIs.
pip install lettermint
from lettermint import Lettermint email = 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"])
from lettermint import AsyncLettermint email = 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()
import base64 # Read and encode your file with open("document.pdf", "rb") as f: content = base64.b64encode(f.read()).decode() # Regular attachment client.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() await AsyncLettermint.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.
from lettermint import Lettermint api = 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(...):
from lettermint import AsyncLettermint api = AsyncLettermint.api("your-api-token") domains = await api.domains.list({"page[size]": "10"}) message_html = await api.messages.html("message-id")
Endpoint groups are available as domains, messages, projects, routes, stats, suppressions, team, and webhooks.
Verify webhook signatures to ensure authenticity:
from lettermint import Webhook # Create a webhook verifier webhook = Webhook(secret="your-webhook-secret") # Verify using headers (recommended) payload = webhook.verify_headers(request.headers, request.body) # Or verify using the signature directly payload = webhook.verify( payload=request.body, signature=request.headers["X-Lettermint-Signature"], ) print(payload["event"])
For one-off verification:
from lettermint import Webhook payload = 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)
from lettermint import Lettermint from lettermint.exceptions import ( 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() except ValidationError as e: # 422 errors (e.g., daily limit exceeded) print(f"Validation error: {e.error_type}") print(f"Response: {e.response_body}") except ClientError as e: # 400 errors print(f"Client error: {e}") except TimeoutError as e: # Request timeout print(f"Timeout: {e}") except HttpRequestError as e: # Other HTTP errors print(f"HTTP error {e.status_code}: {e}")
from lettermint import Webhook from lettermint.exceptions import ( InvalidSignatureError, TimestampToleranceError, JsonDecodeError, WebhookVerificationError, ) try: payload = webhook.verify_headers(headers, body) except InvalidSignatureError: print("Invalid signature - request may be forged") except TimestampToleranceError: print("Timestamp too old - possible replay attack") except JsonDecodeError: print("Invalid JSON in payload") except WebhookVerificationError as e: 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:
# Sync with Lettermint(api_token="your-sending-token") as client: client.email.from_("sender@example.com").to("recipient@example.com").send() # Async async with AsyncLettermint(api_token="your-sending-token") as client: await client.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