0
# SMTP Client Class
1
2
The `SMTP` class provides a full-featured async SMTP client with persistent connection management, comprehensive SMTP command support, and fine-grained control over email operations. It's ideal for applications that need to send multiple emails, require custom SMTP operations, or need detailed control over the connection lifecycle.
3
4
## Capabilities
5
6
### Client Initialization and Connection
7
8
Create and configure SMTP client instances with comprehensive connection options.
9
10
```python { .api }
11
class SMTP:
12
def __init__(
13
self,
14
*,
15
hostname: Optional[str] = None,
16
port: Optional[int] = None,
17
username: Optional[Union[str, bytes]] = None,
18
password: Optional[Union[str, bytes]] = None,
19
local_hostname: Optional[str] = None,
20
source_address: Optional[tuple[str, int]] = None,
21
timeout: Optional[float] = 60,
22
use_tls: bool = False,
23
start_tls: Optional[bool] = None,
24
validate_certs: bool = True,
25
client_cert: Optional[str] = None,
26
client_key: Optional[str] = None,
27
tls_context: Optional[ssl.SSLContext] = None,
28
cert_bundle: Optional[str] = None,
29
socket_path: Optional[SocketPathType] = None,
30
sock: Optional[socket.socket] = None,
31
) -> None:
32
"""
33
Initialize SMTP client with connection parameters.
34
35
Parameters can be provided at initialization or when calling connect().
36
Options provided to connect() override initialization values except timeout.
37
"""
38
39
async def connect(
40
self,
41
*,
42
hostname: Optional[str] = None,
43
port: Optional[int] = None,
44
source_address: Optional[tuple[str, int]] = None,
45
timeout: Optional[float] = None,
46
socket_path: Optional[SocketPathType] = None,
47
sock: Optional[socket.socket] = None,
48
) -> SMTPResponse:
49
"""
50
Connect to SMTP server and perform initial handshake.
51
52
Returns:
53
SMTPResponse from the server greeting
54
55
Raises:
56
- SMTPConnectError: If connection fails
57
- SMTPConnectTimeoutError: If connection times out
58
- SMTPConnectResponseError: If server greeting is invalid
59
"""
60
61
def close(self) -> None:
62
"""Close the connection immediately without QUIT command."""
63
64
async def quit(self) -> SMTPResponse:
65
"""Send QUIT command and close connection gracefully."""
66
```
67
68
### Context Manager Support
69
70
The SMTP client supports async context manager for automatic connection management.
71
72
```python { .api }
73
async def __aenter__(self) -> "SMTP":
74
"""Enter async context manager, connecting if not already connected."""
75
76
async def __aexit__(
77
self,
78
exc_type: Optional[type[BaseException]],
79
exc_val: Optional[BaseException],
80
exc_tb: Optional[TracebackType],
81
) -> None:
82
"""Exit async context manager, closing connection gracefully."""
83
```
84
85
### Connection Status and Properties
86
87
Check connection status and access server capabilities.
88
89
```python { .api }
90
@property
91
def is_connected(self) -> bool:
92
"""True if connected to SMTP server."""
93
94
@property
95
def last_ehlo_response(self) -> Union[SMTPResponse, None]:
96
"""Last EHLO response from server, None if EHLO not performed."""
97
98
@property
99
def is_ehlo_or_helo_needed(self) -> bool:
100
"""True if EHLO or HELO command needs to be sent."""
101
102
@property
103
def supported_auth_methods(self) -> list[str]:
104
"""List of authentication methods supported by server."""
105
106
def supports_extension(self, extension: str, /) -> bool:
107
"""Check if server supports a specific ESMTP extension."""
108
109
def get_transport_info(self, key: str) -> Any:
110
"""Get transport information (SSL info, socket details, etc.)."""
111
```
112
113
### Email Sending Methods
114
115
Send emails using the SMTP client with different message formats.
116
117
```python { .api }
118
async def sendmail(
119
self,
120
sender: str,
121
recipients: Union[str, Sequence[str]],
122
message: Union[str, bytes],
123
*,
124
mail_options: Optional[Sequence[str]] = None,
125
rcpt_options: Optional[Sequence[str]] = None,
126
) -> tuple[dict[str, SMTPResponse], str]:
127
"""
128
Send raw email message.
129
130
Parameters:
131
- sender: From email address
132
- recipients: Recipient email addresses
133
- message: Raw message content
134
- mail_options: Options for MAIL command
135
- rcpt_options: Options for RCPT command
136
137
Returns:
138
Tuple of (recipient_responses, data_response)
139
"""
140
141
async def send_message(
142
self,
143
message: Union[EmailMessage, Message],
144
*,
145
sender: Optional[str] = None,
146
recipients: Optional[Union[str, Sequence[str]]] = None,
147
mail_options: Optional[Sequence[str]] = None,
148
rcpt_options: Optional[Sequence[str]] = None,
149
) -> tuple[dict[str, SMTPResponse], str]:
150
"""
151
Send EmailMessage or Message object.
152
153
Automatically extracts sender and recipients from message headers
154
if not provided explicitly.
155
"""
156
157
def sendmail_sync(
158
self,
159
sender: str,
160
recipients: Union[str, Sequence[str]],
161
message: Union[str, bytes],
162
*,
163
mail_options: Optional[Sequence[str]] = None,
164
rcpt_options: Optional[Sequence[str]] = None,
165
) -> tuple[dict[str, SMTPResponse], str]:
166
"""Synchronous wrapper for sendmail() using asyncio.run()."""
167
168
def send_message_sync(
169
self,
170
message: Union[EmailMessage, Message],
171
*,
172
sender: Optional[str] = None,
173
recipients: Optional[Union[str, Sequence[str]]] = None,
174
mail_options: Optional[Sequence[str]] = None,
175
rcpt_options: Optional[Sequence[str]] = None,
176
) -> tuple[dict[str, SMTPResponse], str]:
177
"""Synchronous wrapper for send_message() using asyncio.run()."""
178
```
179
180
### SMTP Protocol Commands
181
182
Low-level SMTP command methods for advanced use cases.
183
184
```python { .api }
185
async def execute_command(
186
self,
187
command: Union[str, bytes],
188
timeout: Optional[float] = None,
189
) -> SMTPResponse:
190
"""Execute arbitrary SMTP command and return response."""
191
192
async def helo(self, name: Optional[str] = None) -> SMTPResponse:
193
"""Send HELO command with local hostname."""
194
195
async def ehlo(self, name: Optional[str] = None) -> SMTPResponse:
196
"""Send EHLO command with local hostname."""
197
198
async def help(self, command: Optional[str] = None) -> SMTPResponse:
199
"""Send HELP command, optionally for specific command."""
200
201
async def rset(self) -> SMTPResponse:
202
"""Send RSET command to reset session state."""
203
204
async def noop(self) -> SMTPResponse:
205
"""Send NOOP command (no operation)."""
206
207
async def vrfy(self, address: str) -> SMTPResponse:
208
"""Send VRFY command to verify email address."""
209
210
async def expn(self, address: str) -> SMTPResponse:
211
"""Send EXPN command to expand mailing list."""
212
213
async def mail(
214
self,
215
sender: str,
216
options: Optional[Sequence[str]] = None,
217
) -> SMTPResponse:
218
"""Send MAIL FROM command."""
219
220
async def rcpt(
221
self,
222
recipient: str,
223
options: Optional[Sequence[str]] = None,
224
) -> SMTPResponse:
225
"""Send RCPT TO command."""
226
227
async def data(self, message: Union[str, bytes]) -> SMTPResponse:
228
"""Send DATA command with message content."""
229
```
230
231
### TLS and Authentication
232
233
Handle TLS encryption and authentication operations.
234
235
```python { .api }
236
async def starttls(
237
self,
238
tls_context: Optional[ssl.SSLContext] = None,
239
*,
240
validate_certs: bool = True,
241
client_cert: Optional[str] = None,
242
client_key: Optional[str] = None,
243
cert_bundle: Optional[str] = None,
244
) -> SMTPResponse:
245
"""Initiate STARTTLS encryption upgrade."""
246
247
async def login(
248
self,
249
username: Union[str, bytes],
250
password: Union[str, bytes],
251
*,
252
method: Optional[str] = None,
253
) -> SMTPResponse:
254
"""Authenticate with server using best available method."""
255
256
async def auth_crammd5(
257
self,
258
username: Union[str, bytes],
259
password: Union[str, bytes],
260
) -> SMTPResponse:
261
"""Authenticate using CRAM-MD5 method."""
262
263
async def auth_plain(
264
self,
265
username: Union[str, bytes],
266
password: Union[str, bytes],
267
) -> SMTPResponse:
268
"""Authenticate using PLAIN method."""
269
270
async def auth_login(
271
self,
272
username: Union[str, bytes],
273
password: Union[str, bytes],
274
) -> SMTPResponse:
275
"""Authenticate using LOGIN method."""
276
```
277
278
## Usage Examples
279
280
### Basic Client Usage
281
282
```python
283
import asyncio
284
import aiosmtplib
285
from email.message import EmailMessage
286
287
async def basic_client_usage():
288
# Create client instance
289
smtp = aiosmtplib.SMTP(hostname="localhost", port=1025)
290
291
try:
292
# Connect to server
293
await smtp.connect()
294
print(f"Connected: {smtp.is_connected}")
295
296
# Send email
297
message = EmailMessage()
298
message["From"] = "sender@example.com"
299
message["To"] = "recipient@example.com"
300
message["Subject"] = "Test Email"
301
message.set_content("Hello from SMTP client!")
302
303
response = await smtp.send_message(message)
304
print(f"Email sent: {response}")
305
306
finally:
307
# Clean up
308
smtp.close()
309
310
asyncio.run(basic_client_usage())
311
```
312
313
### Context Manager Usage
314
315
```python
316
import asyncio
317
import aiosmtplib
318
319
async def context_manager_usage():
320
# Automatic connection management
321
async with aiosmtplib.SMTP(hostname="localhost", port=1025) as smtp:
322
print(f"Connected: {smtp.is_connected}")
323
324
# Send multiple emails in same session
325
for i in range(3):
326
response = await smtp.sendmail(
327
"sender@example.com",
328
["recipient@example.com"],
329
f"Subject: Message {i}\n\nThis is message {i}"
330
)
331
print(f"Message {i} sent: {response[1]}")
332
333
asyncio.run(context_manager_usage())
334
```
335
336
### Advanced Configuration
337
338
```python
339
import asyncio
340
import ssl
341
import aiosmtplib
342
343
async def advanced_configuration():
344
# Custom SSL context
345
context = ssl.create_default_context()
346
context.check_hostname = False
347
348
# Create client with advanced options
349
smtp = aiosmtplib.SMTP(
350
hostname="smtp.gmail.com",
351
port=587,
352
username="your-email@gmail.com",
353
password="your-password",
354
start_tls=True,
355
validate_certs=True,
356
tls_context=context,
357
timeout=30,
358
local_hostname="my.local.host"
359
)
360
361
async with smtp:
362
# Check server capabilities
363
print(f"Supported auth methods: {smtp.supported_auth_methods}")
364
print(f"Supports PIPELINING: {smtp.supports_extension('PIPELINING')}")
365
print(f"Last EHLO response: {smtp.last_ehlo_response}")
366
367
# Send email with custom options
368
response = await smtp.sendmail(
369
"sender@gmail.com",
370
["recipient@example.com"],
371
"Subject: Advanced Config\n\nEmail with advanced configuration",
372
mail_options=["BODY=8BITMIME"],
373
rcpt_options=["NOTIFY=SUCCESS,FAILURE"]
374
)
375
print(f"Email sent with options: {response}")
376
377
asyncio.run(advanced_configuration())
378
```
379
380
### Error Handling and Recovery
381
382
```python
383
import asyncio
384
import aiosmtplib
385
386
async def error_handling_example():
387
smtp = aiosmtplib.SMTP(hostname="smtp.example.com", port=587)
388
389
try:
390
await smtp.connect()
391
392
# Attempt authentication
393
try:
394
await smtp.login("user", "password")
395
except aiosmtplib.SMTPAuthenticationError:
396
print("Authentication failed, trying different credentials")
397
await smtp.login("user", "different_password")
398
399
# Send email with error handling
400
try:
401
response = await smtp.sendmail(
402
"sender@example.com",
403
["invalid@nonexistent.domain"],
404
"Subject: Test\n\nTest message"
405
)
406
407
# Check individual recipient responses
408
for recipient, smtp_response in response[0].items():
409
if smtp_response.code >= 400:
410
print(f"Failed to send to {recipient}: {smtp_response}")
411
else:
412
print(f"Successfully sent to {recipient}")
413
414
except aiosmtplib.SMTPRecipientsRefused as e:
415
print(f"All recipients refused: {e.recipients}")
416
except aiosmtplib.SMTPSenderRefused as e:
417
print(f"Sender refused: {e.sender}")
418
419
except aiosmtplib.SMTPConnectError as e:
420
print(f"Connection failed: {e}")
421
except aiosmtplib.SMTPTimeoutError as e:
422
print(f"Operation timed out: {e}")
423
finally:
424
if smtp.is_connected:
425
await smtp.quit()
426
427
asyncio.run(error_handling_example())
428
```
429
430
### Low-Level Command Usage
431
432
```python
433
import asyncio
434
import aiosmtplib
435
436
async def low_level_commands():
437
smtp = aiosmtplib.SMTP(hostname="localhost", port=1025)
438
439
async with smtp:
440
# Manual SMTP conversation
441
ehlo_response = await smtp.ehlo("my.domain.com")
442
print(f"EHLO response: {ehlo_response}")
443
444
# Check if authentication is needed
445
if smtp.supported_auth_methods:
446
auth_response = await smtp.login("user", "pass")
447
print(f"AUTH response: {auth_response}")
448
449
# Manual mail transaction
450
mail_response = await smtp.mail("sender@example.com")
451
print(f"MAIL response: {mail_response}")
452
453
rcpt_response = await smtp.rcpt("recipient@example.com")
454
print(f"RCPT response: {rcpt_response}")
455
456
data_response = await smtp.data("Subject: Manual\n\nManual message")
457
print(f"DATA response: {data_response}")
458
459
# Reset for next transaction
460
rset_response = await smtp.rset()
461
print(f"RSET response: {rset_response}")
462
463
asyncio.run(low_level_commands())
464
```
465
466
## Class Constants
467
468
```python { .api }
469
class SMTP:
470
# Preferred authentication methods in order
471
AUTH_METHODS: tuple[str, ...] = ("cram-md5", "plain", "login")
472
473
# Module-level constants
474
SMTP_PORT = 25
475
SMTP_TLS_PORT = 465
476
SMTP_STARTTLS_PORT = 587
477
DEFAULT_TIMEOUT = 60
478
```