Official Python SDK for the EuroMail transactional email service
Project description
euromail
Official Python SDK for the EuroMail transactional email service.
Installation
pip install euromail
Quick Start
from euromail import EuroMail
client = EuroMail(api_key="em_live_your_api_key_here")
result = client.send_email(
from_address="sender@yourdomain.com",
to="recipient@example.com",
subject="Hello from EuroMail",
html_body="<h1>Welcome!</h1><p>Your account is ready.</p>",
)
print(f"Email queued: {result.id}")
Configuration
client = EuroMail(
api_key="em_live_...", # Required
timeout=30.0, # Default: 30 seconds
)
Context manager
The client can be used as a context manager to ensure the HTTP connection is properly closed:
with EuroMail(api_key="em_live_...") as client:
client.send_email(
from_address="noreply@yourdomain.com",
to="user@example.com",
subject="Hello",
text_body="Welcome aboard!",
)
Async client
For async/await applications, use AsyncEuroMail:
from euromail import AsyncEuroMail
async with AsyncEuroMail(api_key="em_live_...") as client:
result = await client.send_email(
from_address="noreply@yourdomain.com",
to="user@example.com",
subject="Hello",
text_body="Welcome aboard!",
)
The async client has the same API as the sync client, with all methods being async.
Sending Emails
Direct send
result = client.send_email(
from_address="noreply@yourdomain.com",
to="user@example.com",
subject="Order Confirmation",
html_body="<h1>Thanks for your order!</h1>",
text_body="Thanks for your order!",
reply_to="support@yourdomain.com",
cc=["manager@yourdomain.com"],
tags=["order", "confirmation"],
metadata={"order_id": "12345"},
)
Send with template
result = client.send_email(
from_address="noreply@yourdomain.com",
to="user@example.com",
template_alias="welcome-email",
template_data={
"name": "John",
"activation_url": "https://example.com/activate/abc123",
},
)
Batch send
from euromail import SendEmailParams
batch = client.send_batch(
emails=[
SendEmailParams(
from_address="noreply@yourdomain.com",
to="user1@example.com",
subject="Hello User 1",
text_body="Welcome aboard!",
),
SendEmailParams(
from_address="noreply@yourdomain.com",
to="user2@example.com",
subject="Hello User 2",
text_body="Welcome aboard!",
),
]
)
print(f"Sent: {len(batch.data)}, Errors: {len(batch.errors)}")
Idempotent sends
client.send_email(
from_address="noreply@yourdomain.com",
to="user@example.com",
subject="Payment Receipt",
html_body="<p>Payment received.</p>",
idempotency_key="payment-receipt-12345",
)
Retrieve and list emails
email = client.get_email("email-uuid")
emails = client.list_emails(page=1, per_page=50, status="delivered")
for e in emails.data:
print(f"{e.to_address}: {e.status}")
Domains
# Add a sending domain
domain = client.add_domain("mail.yourdomain.com")
print("Configure DNS records:", domain.dns_records)
# Trigger verification
verification = client.verify_domain(domain.id)
if verification.fully_verified:
print("Domain verified! SPF, DKIM, DMARC, and return-path all confirmed.")
# List all domains
domains = client.list_domains(page=1, per_page=25)
# Remove a domain
client.delete_domain(domain.id)
Templates
# Create a template with Jinja2-style variables
template = client.create_template(
alias="welcome-email",
name="Welcome Email",
subject="Welcome, {{ name }}!",
html_body="<h1>Hello {{ name }}</h1><p>Welcome to {{ company }}.</p>",
text_body="Hello {{ name }}, welcome to {{ company }}.",
)
# Update a template
client.update_template(template.id, subject="Welcome to {{ company }}, {{ name }}!")
# List and delete
templates = client.list_templates(page=1, per_page=25)
client.delete_template(template.id)
Webhooks
# Subscribe to delivery events
webhook = client.create_webhook(
url="https://yourdomain.com/webhooks/euromail",
events=["delivered", "bounced", "complained", "email.inbound"],
)
# Update webhook
client.update_webhook(
webhook.id,
url="https://yourdomain.com/webhooks/v2",
events=["delivered", "bounced"],
is_active=True,
)
# Send a test event
test = client.test_webhook(webhook.id)
# List and delete
webhooks = client.list_webhooks()
client.delete_webhook(webhook.id)
Supported events: sent, delivered, bounced, opened, clicked, complained, email.inbound
Suppressions
# Suppress an address manually
client.add_suppression("bounced@example.com", reason="hard_bounce")
# List all suppressions
suppressions = client.list_suppressions(page=1, per_page=50)
# Remove a suppression
client.delete_suppression("bounced@example.com")
Contact Lists
# Create a list with double opt-in
contact_list = client.create_contact_list(
name="Newsletter",
description="Monthly product updates",
double_opt_in=True,
)
# Add a single contact
contact = client.add_contact(
contact_list.id,
email="user@example.com",
metadata={"first_name": "Jane", "source": "signup"},
)
# Bulk add contacts
result = client.bulk_add_contacts(
contact_list.id,
contacts=[
{"email": "a@example.com", "metadata": {"name": "Alice"}},
{"email": "b@example.com", "metadata": {"name": "Bob"}},
],
)
print(f"Inserted: {result.inserted} of {result.total_requested}")
# List contacts with filters
contacts = client.list_contacts(contact_list.id, page=1, per_page=50, status="active")
# Remove contact and delete list
client.remove_contact(contact_list.id, "user@example.com")
client.delete_contact_list(contact_list.id)
Inbound Email
# List received emails
inbound = client.list_inbound_emails(page=1, per_page=25)
# Get details
email = client.get_inbound_email("inbound-uuid")
print(f"From: {email.from_address}, Subject: {email.subject}")
# Delete
client.delete_inbound_email("inbound-uuid")
Inbound Routes
# Route incoming email to a webhook
route = client.create_inbound_route(
domain_id="domain-uuid",
pattern="support@",
match_type="prefix",
priority=10,
webhook_url="https://yourdomain.com/inbound/support",
)
# Update a route
client.update_inbound_route(route.id, webhook_url="https://yourdomain.com/inbound/v2", is_active=True)
# Catch-all route
client.create_inbound_route(
domain_id="domain-uuid",
pattern="*",
match_type="catch_all",
priority=100,
)
# List and delete
routes = client.list_inbound_routes()
client.delete_inbound_route(route.id)
Analytics
# Overview for the last 30 days
overview = client.get_analytics_overview(period="30d")
print(f"Delivery rate: {overview.delivery_rate}%")
# Custom date range
custom = client.get_analytics_overview(from_date="2025-01-01", to_date="2025-01-31")
# Time series data
timeseries = client.get_analytics_timeseries(period="7d", metrics=["sent", "delivered", "bounced"])
# Per-domain breakdown
domains = client.get_analytics_domains(period="30d", limit=10)
# Export as CSV
csv_data = client.export_analytics_csv(period="30d")
Account
account = client.get_account()
print(f"Plan: {account.plan}, Used: {account.emails_sent_this_month}/{account.monthly_quota}")
# Export all account data (GDPR)
export_data = client.export_account()
# Delete account permanently
client.delete_account()
Audit Logs
logs = client.list_audit_logs(page=1, per_page=50)
for log in logs.data:
print(f"{log.created_at}: {log.action} on {log.resource_type}")
Dead Letters
# List failed emails
dead_letters = client.list_dead_letters(count=20)
# Retry delivery
client.retry_dead_letter("dead-letter-uuid")
# Remove permanently
client.delete_dead_letter("dead-letter-uuid")
Error Handling
All API errors raise typed exceptions:
from euromail import (
EuroMail,
EuroMailError,
AuthenticationError,
RateLimitError,
ValidationError,
)
try:
client.send_email(...)
except AuthenticationError:
# Invalid or missing API key (401)
print("Check your API key")
except ValidationError as e:
# Invalid request parameters (422)
print(f"{e.code}: {e.message}")
except RateLimitError as e:
# Too many requests (429)
print(f"Retry after {e.retry_after} seconds")
except EuroMailError as e:
# Other API errors (4xx/5xx)
print(f"[{e.status}] {e.code}: {e.message}")
| Exception | HTTP Status | Description |
|---|---|---|
AuthenticationError |
401 | Invalid or missing API key |
ValidationError |
422 | Invalid request parameters |
RateLimitError |
429 | Too many requests (includes retry_after) |
EuroMailError |
4xx/5xx | Base class for all API errors |
API Reference
| Category | Method | Description |
|---|---|---|
| Emails | send_email(...) |
Send a single email |
send_batch(emails=...) |
Send up to 500 emails in one request | |
get_email(id) |
Get email details and status | |
list_emails(...) |
List emails with pagination and status filter | |
| Templates | create_template(...) |
Create an email template |
get_template(id) |
Get template by ID | |
update_template(id, ...) |
Update template fields | |
delete_template(id) |
Delete a template | |
list_templates(...) |
List templates with pagination | |
| Domains | add_domain(domain) |
Register a sending domain |
get_domain(id) |
Get domain details and DNS records | |
verify_domain(id) |
Trigger DNS verification | |
delete_domain(id) |
Remove a domain | |
list_domains(...) |
List domains with pagination | |
| Webhooks | create_webhook(...) |
Subscribe to events |
get_webhook(id) |
Get webhook details | |
update_webhook(id, ...) |
Update URL, events, or status | |
test_webhook(id) |
Send a test event | |
delete_webhook(id) |
Remove a webhook | |
list_webhooks(...) |
List webhooks with pagination | |
| Suppressions | add_suppression(email, ...) |
Suppress an email address |
delete_suppression(email) |
Remove a suppression | |
list_suppressions(...) |
List suppressions with pagination | |
| Contact Lists | create_contact_list(...) |
Create a contact list |
get_contact_list(id) |
Get list details | |
update_contact_list(id, ...) |
Update list settings | |
delete_contact_list(id) |
Delete a list | |
list_contact_lists() |
List all contact lists | |
add_contact(list_id, ...) |
Add a contact to a list | |
bulk_add_contacts(list_id, ...) |
Add multiple contacts | |
list_contacts(list_id, ...) |
List contacts with filters | |
remove_contact(list_id, email) |
Remove a contact | |
| Inbound | list_inbound_emails(...) |
List received emails |
get_inbound_email(id) |
Get inbound email details | |
delete_inbound_email(id) |
Delete an inbound email | |
| Inbound Routes | create_inbound_route(...) |
Create a routing rule |
get_inbound_route(id) |
Get route details | |
update_inbound_route(id, ...) |
Update a route | |
delete_inbound_route(id) |
Delete a route | |
list_inbound_routes(...) |
List routes with pagination | |
| Analytics | get_analytics_overview(...) |
Aggregated delivery stats |
get_analytics_timeseries(...) |
Daily metrics over time | |
get_analytics_domains(...) |
Per-domain breakdown | |
export_analytics_csv(...) |
Export stats as CSV | |
| Audit Logs | list_audit_logs(...) |
List account activity |
| Dead Letters | list_dead_letters(...) |
List permanently failed emails |
retry_dead_letter(id) |
Retry delivery | |
delete_dead_letter(id) |
Remove from dead letter queue | |
| Account | get_account() |
Get account info and quota |
export_account() |
Export all account data | |
delete_account() |
Permanently delete account |
Requirements
- Python 3.9+
- httpx >= 0.27
License
MIT
Project details
Release history Release notifications | RSS feed
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 euromail-0.1.0.tar.gz.
File metadata
- Download URL: euromail-0.1.0.tar.gz
- Upload date:
- Size: 21.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea10a9fa7bdff4aceeac56beba6b8cf549a912833fa570d38e2d1ebb63c688da
|
|
| MD5 |
1faf6083a21994b40d98e66db32758a6
|
|
| BLAKE2b-256 |
a11c02d7e60b3f696b0b21e91514dbceda3bc1b77874a4ef51d2d542ec6bd397
|
Provenance
The following attestation bundles were made for euromail-0.1.0.tar.gz:
Publisher:
publish.yml on kalle-works/euromail-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
euromail-0.1.0.tar.gz -
Subject digest:
ea10a9fa7bdff4aceeac56beba6b8cf549a912833fa570d38e2d1ebb63c688da - Sigstore transparency entry: 1233263604
- Sigstore integration time:
-
Permalink:
kalle-works/euromail-python@c0f5128336fd7c092e1a6b70654f90e01f710fe1 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/kalle-works
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c0f5128336fd7c092e1a6b70654f90e01f710fe1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file euromail-0.1.0-py3-none-any.whl.
File metadata
- Download URL: euromail-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d2fb52a945b857c6993664ee3b1b161cdbabe16bd8d4b0ea5711d3a2303886d0
|
|
| MD5 |
d3982e73d93f83b53fb4b50dc406c919
|
|
| BLAKE2b-256 |
66509823700fdfb4fa28d6270ee7d39b85768df222a9b12306696404d9f2db36
|
Provenance
The following attestation bundles were made for euromail-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on kalle-works/euromail-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
euromail-0.1.0-py3-none-any.whl -
Subject digest:
d2fb52a945b857c6993664ee3b1b161cdbabe16bd8d4b0ea5711d3a2303886d0 - Sigstore transparency entry: 1233263644
- Sigstore integration time:
-
Permalink:
kalle-works/euromail-python@c0f5128336fd7c092e1a6b70654f90e01f710fe1 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/kalle-works
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c0f5128336fd7c092e1a6b70654f90e01f710fe1 -
Trigger Event:
push
-
Statement type: