msmtp 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- msmtp-1.0.0/CHANGELOG.md +93 -0
- msmtp-1.0.0/LICENSE +21 -0
- msmtp-1.0.0/MANIFEST.in +5 -0
- msmtp-1.0.0/PKG-INFO +392 -0
- msmtp-1.0.0/README.md +356 -0
- msmtp-1.0.0/docs/Performance.md +635 -0
- msmtp-1.0.0/examples/basic_send.py +40 -0
- msmtp-1.0.0/examples/production_bulk.py +101 -0
- msmtp-1.0.0/pyproject.toml +110 -0
- msmtp-1.0.0/setup.cfg +4 -0
- msmtp-1.0.0/src/msmtp/__init__.py +119 -0
- msmtp-1.0.0/src/msmtp/circuit_breaker.py +276 -0
- msmtp-1.0.0/src/msmtp/connection_pool.py +302 -0
- msmtp-1.0.0/src/msmtp/exceptions.py +93 -0
- msmtp-1.0.0/src/msmtp/rate_limiter.py +252 -0
- msmtp-1.0.0/src/msmtp/retry_queue.py +333 -0
- msmtp-1.0.0/src/msmtp/sender.py +486 -0
- msmtp-1.0.0/src/msmtp/types.py +181 -0
- msmtp-1.0.0/src/msmtp/validation.py +154 -0
- msmtp-1.0.0/src/msmtp.egg-info/PKG-INFO +392 -0
- msmtp-1.0.0/src/msmtp.egg-info/SOURCES.txt +29 -0
- msmtp-1.0.0/src/msmtp.egg-info/dependency_links.txt +1 -0
- msmtp-1.0.0/src/msmtp.egg-info/requires.txt +12 -0
- msmtp-1.0.0/src/msmtp.egg-info/top_level.txt +1 -0
- msmtp-1.0.0/tests/test_comprehensive.py +532 -0
- msmtp-1.0.0/tests/test_connection_pool.py +298 -0
- msmtp-1.0.0/tests/test_edge_cases.py +237 -0
- msmtp-1.0.0/tests/test_integration.py +178 -0
- msmtp-1.0.0/tests/test_rate_limiter.py +163 -0
- msmtp-1.0.0/tests/test_retry_queue.py +283 -0
- msmtp-1.0.0/tests/test_sender.py +489 -0
msmtp-1.0.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Mercury SMTP will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2024-01-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Core Features:**
|
|
12
|
+
- Async SMTP sender with `aiosmtplib` backend
|
|
13
|
+
- Connection pooling (10-100× throughput improvement)
|
|
14
|
+
- Circuit breaker pattern (prevent cascading failures)
|
|
15
|
+
- Token bucket rate limiter (prevent server bans)
|
|
16
|
+
- Retry queue with exponential backoff
|
|
17
|
+
|
|
18
|
+
- **Load Balancing:**
|
|
19
|
+
- Multi-server support with 3 strategies (round-robin, weighted, priority)
|
|
20
|
+
- Automatic failover to backup servers
|
|
21
|
+
- Per-server circuit breaker isolation
|
|
22
|
+
|
|
23
|
+
- **Resilience:**
|
|
24
|
+
- Automatic retry for transient errors (network, timeouts)
|
|
25
|
+
- Exponential backoff with jitter
|
|
26
|
+
- Configurable retry limits and delays
|
|
27
|
+
- Dead letter queue for permanent failures
|
|
28
|
+
|
|
29
|
+
- **Performance:**
|
|
30
|
+
- Connection reuse (reduces TLS handshake overhead)
|
|
31
|
+
- Concurrent bulk sending (10-1000× emails/min)
|
|
32
|
+
- Latency tracking (P50/P95/P99 metrics)
|
|
33
|
+
- Resource profiling (memory, CPU, network)
|
|
34
|
+
|
|
35
|
+
- **Configuration:**
|
|
36
|
+
- YAML/JSON configuration support
|
|
37
|
+
- Environment variable overrides
|
|
38
|
+
- Per-server tuning (rate limits, circuit breaker thresholds)
|
|
39
|
+
- Production-ready defaults
|
|
40
|
+
|
|
41
|
+
- **Documentation:**
|
|
42
|
+
- Comprehensive README with quick start
|
|
43
|
+
- Performance benchmarks (throughput, latency, resource usage)
|
|
44
|
+
- Production tuning guide
|
|
45
|
+
- Code examples (basic, bulk, production)
|
|
46
|
+
- API reference
|
|
47
|
+
|
|
48
|
+
- **Testing:**
|
|
49
|
+
- Unit tests for core components
|
|
50
|
+
- Integration tests for multi-server scenarios
|
|
51
|
+
- pytest-asyncio test suite
|
|
52
|
+
- 90%+ code coverage target
|
|
53
|
+
|
|
54
|
+
### Dependencies
|
|
55
|
+
- **Core:**
|
|
56
|
+
- Python >=3.10
|
|
57
|
+
- aiosmtplib >=3.0.0
|
|
58
|
+
|
|
59
|
+
- **Optional:**
|
|
60
|
+
- redis >=5.0.0 (distributed rate limiting)
|
|
61
|
+
|
|
62
|
+
- **Development:**
|
|
63
|
+
- pytest >=8.0.0
|
|
64
|
+
- pytest-asyncio >=0.23.0
|
|
65
|
+
- pytest-cov >=4.1.0
|
|
66
|
+
- ruff >=0.1.14
|
|
67
|
+
- mypy >=1.8.0
|
|
68
|
+
|
|
69
|
+
### Performance Highlights
|
|
70
|
+
- **Throughput:** 500-30,000 emails/min (single worker)
|
|
71
|
+
- **Latency:** P50 50ms, P99 300ms (with pooling)
|
|
72
|
+
- **Resource Usage:** 50 KB/connection memory, <5% CPU
|
|
73
|
+
- **Scaling:** Linear up to 4 workers (19K emails/min)
|
|
74
|
+
|
|
75
|
+
### License
|
|
76
|
+
- MIT License
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## [Unreleased]
|
|
81
|
+
|
|
82
|
+
### Planned Features
|
|
83
|
+
- [ ] Redis-backed distributed rate limiter
|
|
84
|
+
- [ ] Prometheus metrics exporter
|
|
85
|
+
- [ ] Webhook notifications for failures
|
|
86
|
+
- [ ] S3/Azure Blob storage for dead letter queue
|
|
87
|
+
- [ ] OpenTelemetry tracing
|
|
88
|
+
- [ ] gRPC API for remote control
|
|
89
|
+
- [ ] Dashboard UI for monitoring
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
[1.0.0]: https://github.com/mercury/mercury-smtp/releases/tag/v1.0.0
|
msmtp-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 msmtp
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
msmtp-1.0.0/MANIFEST.in
ADDED
msmtp-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: msmtp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Production-grade async SMTP sender with connection pooling, circuit breakers, and rate limiting
|
|
5
|
+
Author-email: MerCury Team <contact@mercury-email.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/0fukuAkz/MerCury
|
|
8
|
+
Project-URL: Documentation, https://github.com/0fukuAkz/MerCury/tree/main/mercury-smtp/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/0fukuAkz/MerCury
|
|
10
|
+
Project-URL: Issues, https://github.com/0fukuAkz/MerCury/issues
|
|
11
|
+
Keywords: smtp,email,async,aiosmtplib,circuit-breaker,rate-limiting
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Communications :: Email
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Framework :: AsyncIO
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: aiosmtplib>=3.0.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.8.0; extra == "dev"
|
|
33
|
+
Provides-Extra: redis
|
|
34
|
+
Requires-Dist: redis>=5.0.0; extra == "redis"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# Mercury SMTP - Production-Grade Async Email Sender
|
|
38
|
+
|
|
39
|
+
A high-performance, production-ready async SMTP library with built-in resilience patterns:
|
|
40
|
+
|
|
41
|
+
- 🔄 **Connection Pooling**: Reusable SMTP connections with health checks
|
|
42
|
+
- ⚡ **Circuit Breaker**: Automatic failover when servers fail
|
|
43
|
+
- 🚦 **Rate Limiting**: Token bucket algorithm for send-rate control
|
|
44
|
+
- 🔁 **Retry Queue**: Exponential backoff for transient failures
|
|
45
|
+
- 📊 **Metrics**: Built-in latency and throughput tracking
|
|
46
|
+
- 🎯 **Load Balancing**: Weighted, round-robin, or priority-based server selection
|
|
47
|
+
|
|
48
|
+
Extracted from [MerCury](https://github.com/0fukuAkz/MerCury), a production email automation platform handling 1M+ emails/day.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install msmtp
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**With Redis support** (for distributed rate limiting):
|
|
59
|
+
```bash
|
|
60
|
+
pip install msmtp[redis]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Quick Start
|
|
66
|
+
|
|
67
|
+
### Basic Usage
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import asyncio
|
|
71
|
+
from msmtp import AsyncSMTPSender, SMTPServerConfig
|
|
72
|
+
|
|
73
|
+
# Configure SMTP server
|
|
74
|
+
server = SMTPServerConfig(
|
|
75
|
+
host="smtp.gmail.com",
|
|
76
|
+
port=587,
|
|
77
|
+
username="user@gmail.com",
|
|
78
|
+
password="app-password",
|
|
79
|
+
use_tls=True,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
async def send_email():
|
|
83
|
+
async with AsyncSMTPSender([server]) as sender:
|
|
84
|
+
result = await sender.send(
|
|
85
|
+
from_addr="sender@example.com",
|
|
86
|
+
to_addrs=["recipient@example.com"],
|
|
87
|
+
subject="Hello from Mercury SMTP",
|
|
88
|
+
body_text="Plain text body",
|
|
89
|
+
body_html="<p>HTML body</p>",
|
|
90
|
+
)
|
|
91
|
+
print(f"Sent: {result.success}")
|
|
92
|
+
|
|
93
|
+
asyncio.run(send_email())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Production Configuration
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from msmtp import (
|
|
100
|
+
AsyncSMTPSender,
|
|
101
|
+
SMTPServerConfig,
|
|
102
|
+
CircuitBreakerConfig,
|
|
103
|
+
RateLimiterConfig,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Multiple servers with circuit breakers and rate limits
|
|
107
|
+
servers = [
|
|
108
|
+
SMTPServerConfig(
|
|
109
|
+
name="primary",
|
|
110
|
+
host="smtp1.example.com",
|
|
111
|
+
port=587,
|
|
112
|
+
username="user",
|
|
113
|
+
password="pass",
|
|
114
|
+
weight=10, # Load balancing weight
|
|
115
|
+
max_per_hour=10000, # Rate limit
|
|
116
|
+
circuit_breaker=CircuitBreakerConfig(
|
|
117
|
+
failure_threshold=5,
|
|
118
|
+
timeout_seconds=60,
|
|
119
|
+
),
|
|
120
|
+
),
|
|
121
|
+
SMTPServerConfig(
|
|
122
|
+
name="backup",
|
|
123
|
+
host="smtp2.example.com",
|
|
124
|
+
port=587,
|
|
125
|
+
username="user",
|
|
126
|
+
password="pass",
|
|
127
|
+
weight=5,
|
|
128
|
+
priority=1, # Lower priority (fallback)
|
|
129
|
+
),
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
async def send_bulk():
|
|
133
|
+
async with AsyncSMTPSender(
|
|
134
|
+
servers=servers,
|
|
135
|
+
rate_limiter=RateLimiterConfig(per_second=10.0),
|
|
136
|
+
max_retries=3,
|
|
137
|
+
) as sender:
|
|
138
|
+
# Send to multiple recipients
|
|
139
|
+
results = await sender.send_bulk([
|
|
140
|
+
{
|
|
141
|
+
"from_addr": "sender@example.com",
|
|
142
|
+
"to_addrs": ["user1@example.com"],
|
|
143
|
+
"subject": "Welcome!",
|
|
144
|
+
"body_text": "Hello User 1",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"from_addr": "sender@example.com",
|
|
148
|
+
"to_addrs": ["user2@example.com"],
|
|
149
|
+
"subject": "Welcome!",
|
|
150
|
+
"body_text": "Hello User 2",
|
|
151
|
+
},
|
|
152
|
+
])
|
|
153
|
+
|
|
154
|
+
print(f"Success: {results.success_count}/{results.total}")
|
|
155
|
+
|
|
156
|
+
asyncio.run(send_bulk())
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Key Features
|
|
162
|
+
|
|
163
|
+
### Connection Pooling
|
|
164
|
+
|
|
165
|
+
Maintains persistent SMTP connections per server with automatic health checks:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from msmtp import SMTPConnectionPool
|
|
169
|
+
|
|
170
|
+
pool = SMTPConnectionPool(
|
|
171
|
+
server=server,
|
|
172
|
+
max_connections=10,
|
|
173
|
+
health_check_interval=60, # seconds
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Connections are reused across sends
|
|
177
|
+
async with pool.acquire() as conn:
|
|
178
|
+
await conn.send_message(msg)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Benefits:**
|
|
182
|
+
- Reduces connection overhead (TLS handshake ~100-500ms saved per send)
|
|
183
|
+
- Health checks prevent using stale connections
|
|
184
|
+
- Automatic connection recycling
|
|
185
|
+
|
|
186
|
+
### Circuit Breaker
|
|
187
|
+
|
|
188
|
+
Prevents cascading failures by temporarily disabling failing servers:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from msmtp import CircuitBreakerConfig
|
|
192
|
+
|
|
193
|
+
config = CircuitBreakerConfig(
|
|
194
|
+
failure_threshold=5, # Open after 5 failures
|
|
195
|
+
success_threshold=2, # Close after 2 successes
|
|
196
|
+
timeout_seconds=60, # Half-open retry after 60s
|
|
197
|
+
monitor_window_seconds=300,
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**States:**
|
|
202
|
+
- **CLOSED**: Normal operation
|
|
203
|
+
- **OPEN**: Too many failures, stop trying (fails fast)
|
|
204
|
+
- **HALF_OPEN**: Testing recovery with limited traffic
|
|
205
|
+
|
|
206
|
+
### Rate Limiting
|
|
207
|
+
|
|
208
|
+
Token bucket algorithm for precise send-rate control:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from msmtp import RateLimiterConfig
|
|
212
|
+
|
|
213
|
+
limiter = RateLimiterConfig(
|
|
214
|
+
per_second=10.0, # 10 emails/second
|
|
215
|
+
per_minute=500.0, # 500 emails/minute
|
|
216
|
+
per_hour=10000.0, # 10K emails/hour
|
|
217
|
+
burst_size=20, # Allow bursts up to 20
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Storage backends:**
|
|
222
|
+
- **In-memory** (default): Single-process
|
|
223
|
+
- **Redis**: Distributed rate limiting across workers
|
|
224
|
+
|
|
225
|
+
### Retry Queue
|
|
226
|
+
|
|
227
|
+
Automatic retry with exponential backoff for transient errors:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from msmtp import RetryConfig
|
|
231
|
+
|
|
232
|
+
retry = RetryConfig(
|
|
233
|
+
max_attempts=3,
|
|
234
|
+
base_delay=60, # Start with 60s
|
|
235
|
+
max_delay=3600, # Cap at 1 hour
|
|
236
|
+
exponential_base=2, # Delay *= 2 each retry
|
|
237
|
+
)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Retry delays:** 60s → 120s → 240s → permanent failure
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Performance Characteristics
|
|
245
|
+
|
|
246
|
+
See [docs/Performance.md](docs/Performance.md) for detailed benchmarks.
|
|
247
|
+
|
|
248
|
+
### Throughput
|
|
249
|
+
|
|
250
|
+
| Configuration | Emails/Min | Notes |
|
|
251
|
+
|--------------|------------|-------|
|
|
252
|
+
| Single connection | 500-1000 | Depends on SMTP server |
|
|
253
|
+
| Connection pool (10 conns) | 5000-10000 | Linear scaling |
|
|
254
|
+
| Multi-server (3 servers) | 15000-30000 | With load balancing |
|
|
255
|
+
|
|
256
|
+
### Latency
|
|
257
|
+
|
|
258
|
+
| Operation | P50 | P95 | P99 |
|
|
259
|
+
|-----------|-----|-----|-----|
|
|
260
|
+
| Connection handshake | 100ms | 300ms | 500ms |
|
|
261
|
+
| Send (pooled) | 50ms | 150ms | 300ms |
|
|
262
|
+
| Send (new connection) | 150ms | 450ms | 800ms |
|
|
263
|
+
|
|
264
|
+
### Resource Usage
|
|
265
|
+
|
|
266
|
+
- **Memory**: 5-10 MB baseline + ~50 KB per connection
|
|
267
|
+
- **CPU**: <5% (I/O bound)
|
|
268
|
+
- **Connections**: 1-10 per server (configurable)
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Advanced Usage
|
|
273
|
+
|
|
274
|
+
### Custom Error Handling
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
from msmtp import SMTPError, SMTPAuthenticationError
|
|
278
|
+
|
|
279
|
+
async def send_with_retry():
|
|
280
|
+
try:
|
|
281
|
+
result = await sender.send(...)
|
|
282
|
+
except SMTPAuthenticationError:
|
|
283
|
+
# Update credentials
|
|
284
|
+
await update_smtp_credentials()
|
|
285
|
+
except SMTPError as e:
|
|
286
|
+
# Handle other SMTP errors
|
|
287
|
+
logger.error(f"Send failed: {e}")
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Metrics Integration
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
from msmtp import AsyncSMTPSender
|
|
294
|
+
|
|
295
|
+
class MetricsSender(AsyncSMTPSender):
|
|
296
|
+
async def _record_send(self, result):
|
|
297
|
+
# Your metrics system
|
|
298
|
+
prometheus.inc("emails_sent", labels={"status": result.status})
|
|
299
|
+
await super()._record_send(result)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Server Selection Strategy
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
from msmtp import LoadBalancingStrategy
|
|
306
|
+
|
|
307
|
+
sender = AsyncSMTPSender(
|
|
308
|
+
servers=servers,
|
|
309
|
+
strategy=LoadBalancingStrategy.WEIGHTED, # or ROUND_ROBIN, PRIORITY
|
|
310
|
+
)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Testing
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
# Run tests
|
|
319
|
+
pytest
|
|
320
|
+
|
|
321
|
+
# With coverage
|
|
322
|
+
pytest --cov=msmtp --cov-report=html
|
|
323
|
+
|
|
324
|
+
# Async tests
|
|
325
|
+
pytest tests/test_sender.py -v
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Architecture
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
┌─────────────────────────────────────────────────────┐
|
|
334
|
+
│ AsyncSMTPSender │
|
|
335
|
+
│ (Orchestration, load balancing, error recovery) │
|
|
336
|
+
└─────────────────────────────────────────────────────┘
|
|
337
|
+
│
|
|
338
|
+
┌───────────────┼───────────────┐
|
|
339
|
+
▼ ▼ ▼
|
|
340
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
341
|
+
│ Connection │ │ Circuit │ │ Rate │
|
|
342
|
+
│ Pool │ │ Breaker │ │ Limiter │
|
|
343
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
344
|
+
│ │ │
|
|
345
|
+
└───────────────┼───────────────┘
|
|
346
|
+
▼
|
|
347
|
+
┌──────────────┐
|
|
348
|
+
│ aiosmtplib │
|
|
349
|
+
│ (SMTP client)│
|
|
350
|
+
└──────────────┘
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Comparison
|
|
356
|
+
|
|
357
|
+
| Feature | msmtp | aiosmtplib | smtplib |
|
|
358
|
+
|---------|--------------|------------|---------|
|
|
359
|
+
| Async | ✅ | ✅ | ❌ |
|
|
360
|
+
| Connection pooling | ✅ | ❌ | ❌ |
|
|
361
|
+
| Circuit breaker | ✅ | ❌ | ❌ |
|
|
362
|
+
| Rate limiting | ✅ | ❌ | ❌ |
|
|
363
|
+
| Auto retry | ✅ | ❌ | ❌ |
|
|
364
|
+
| Load balancing | ✅ | ❌ | ❌ |
|
|
365
|
+
| Production-ready | ✅ | ⚠️ | ⚠️ |
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Contributing
|
|
370
|
+
|
|
371
|
+
Contributions welcome! See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## License
|
|
376
|
+
|
|
377
|
+
MIT License - see [LICENSE](../LICENSE) for details.
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Related Projects
|
|
382
|
+
|
|
383
|
+
- [MerCury](https://github.com/0fukuAkz/MerCury) - Full-featured email automation platform
|
|
384
|
+
- [aiosmtplib](https://github.com/cole/aiosmtplib) - Async SMTP client (underlying transport)
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Support
|
|
389
|
+
|
|
390
|
+
- 📖 [Documentation](docs/)
|
|
391
|
+
- 🐛 [Issue Tracker](https://github.com/0fukuAkz/msmtp/issues)
|
|
392
|
+
- 💬 [Discussions](https://github.com/0fukuAkz/msmtp/discussions)
|