0
# Utility Functions
1
2
aiosmtplib provides utility functions for authentication, email message processing, and ESMTP extension handling. These functions are useful for custom implementations, debugging, and applications that need fine-grained control over SMTP operations.
3
4
## Capabilities
5
6
### Authentication Utilities
7
8
Functions for encoding authentication credentials in various SMTP authentication protocols.
9
10
```python
11
from aiosmtplib.auth import auth_plain_encode, auth_login_encode, auth_crammd5_verify
12
```
13
14
```python { .api }
15
def auth_plain_encode(username: str, password: str) -> bytes:
16
"""
17
Encode credentials for PLAIN authentication method.
18
19
Parameters:
20
- username: Authentication username
21
- password: Authentication password
22
23
Returns:
24
Encoded credentials as bytes suitable for PLAIN AUTH command
25
"""
26
27
def auth_login_encode(username: str, password: str) -> tuple[bytes, bytes]:
28
"""
29
Encode credentials for LOGIN authentication method.
30
31
Parameters:
32
- username: Authentication username
33
- password: Authentication password
34
35
Returns:
36
Tuple of (encoded_username, encoded_password) as bytes
37
"""
38
39
def auth_crammd5_verify(username: str, password: str, challenge: bytes) -> bytes:
40
"""
41
Generate CRAM-MD5 authentication response.
42
43
Parameters:
44
- username: Authentication username
45
- password: Authentication password
46
- challenge: Challenge bytes from server
47
48
Returns:
49
CRAM-MD5 response bytes
50
"""
51
```
52
53
### Email Message Utilities
54
55
Functions for processing email messages and extracting address information.
56
57
```python
58
from aiosmtplib.email import extract_recipients, extract_sender, flatten_message, parse_address, quote_address
59
```
60
61
```python { .api }
62
def extract_recipients(message: Union[EmailMessage, Message]) -> list[str]:
63
"""
64
Extract all recipient addresses from email message headers.
65
66
Processes To, Cc, and Bcc headers to build complete recipient list.
67
68
Parameters:
69
- message: EmailMessage or Message object
70
71
Returns:
72
List of recipient email addresses
73
"""
74
75
def extract_sender(message: Union[EmailMessage, Message]) -> Optional[str]:
76
"""
77
Extract sender address from email message headers.
78
79
Checks From header for sender address.
80
81
Parameters:
82
- message: EmailMessage or Message object
83
84
Returns:
85
Sender email address or None if not found
86
"""
87
88
def flatten_message(
89
message: Union[EmailMessage, Message],
90
*,
91
utf8: bool = False,
92
cte_type: str = "8bit"
93
) -> bytes:
94
"""
95
Convert email message to bytes for SMTP transmission.
96
97
Parameters:
98
- message: EmailMessage or Message object
99
- utf8: Use UTF-8 encoding
100
- cte_type: Content transfer encoding type
101
102
Returns:
103
Message as bytes ready for SMTP DATA command
104
"""
105
106
def parse_address(address: str) -> str:
107
"""
108
Parse and normalize email address string.
109
110
Handles various email address formats and extracts the actual
111
email address from display name formats.
112
113
Parameters:
114
- address: Email address string (may include display name)
115
116
Returns:
117
Normalized email address
118
"""
119
120
def quote_address(address: str) -> str:
121
"""
122
Quote email address according to RFC 821 rules.
123
124
Adds angle brackets around address if needed for SMTP commands.
125
126
Parameters:
127
- address: Email address string
128
129
Returns:
130
Properly quoted address for SMTP commands
131
"""
132
```
133
134
### ESMTP Extension Utilities
135
136
Functions for parsing and handling ESMTP server extensions.
137
138
```python
139
from aiosmtplib.esmtp import parse_esmtp_extensions
140
```
141
142
```python { .api }
143
def parse_esmtp_extensions(message: str) -> tuple[dict[str, str], list[str]]:
144
"""
145
Parse EHLO response into extensions dictionary and auth methods list.
146
147
Parameters:
148
- message: Raw EHLO response message from server
149
150
Returns:
151
Tuple of (extensions_dict, auth_methods_list) where:
152
- extensions_dict maps extension names to their parameters
153
- auth_methods_list contains supported authentication methods
154
"""
155
```
156
157
### Low-Level Protocol Access
158
159
Advanced protocol class for custom SMTP implementations.
160
161
```python { .api }
162
class SMTPProtocol:
163
"""
164
Low-level asyncio protocol for SMTP communication.
165
166
Advanced users can use this for custom SMTP implementations
167
that need direct protocol access.
168
"""
169
def __init__(
170
self,
171
loop: Optional[asyncio.AbstractEventLoop] = None,
172
connection_lost_callback: Optional[Callable[[], None]] = None,
173
) -> None: ...
174
```
175
176
## Usage Examples
177
178
### Authentication Utilities
179
180
```python
181
from aiosmtplib.auth import auth_plain_encode, auth_login_encode, auth_crammd5_verify
182
183
# Encode credentials for different auth methods
184
username = "user@example.com"
185
password = "secret123"
186
187
# PLAIN authentication
188
plain_creds = auth_plain_encode(username, password)
189
print(f"PLAIN credentials: {plain_creds}")
190
191
# LOGIN authentication
192
login_user, login_pass = auth_login_encode(username, password)
193
print(f"LOGIN username: {login_user}")
194
print(f"LOGIN password: {login_pass}")
195
196
# CRAM-MD5 authentication (requires server challenge)
197
challenge = b"<12345.678@example.com>"
198
crammd5_response = auth_crammd5_verify(username, password, challenge)
199
print(f"CRAM-MD5 response: {crammd5_response}")
200
```
201
202
### Email Message Processing
203
204
```python
205
from aiosmtplib.email import extract_recipients, extract_sender, flatten_message, parse_address, quote_address
206
from email.message import EmailMessage
207
208
# Create test message
209
message = EmailMessage()
210
message["From"] = "sender@example.com"
211
message["To"] = "recipient1@example.com, recipient2@example.com"
212
message["Cc"] = "cc@example.com"
213
message["Bcc"] = "bcc@example.com"
214
message["Subject"] = "Test Message"
215
message.set_content("This is a test message.")
216
217
# Extract recipients
218
recipients = extract_recipients(message)
219
print(f"All recipients: {recipients}")
220
# Output: ['recipient1@example.com', 'recipient2@example.com', 'cc@example.com', 'bcc@example.com']
221
222
# Extract sender
223
sender = extract_sender(message)
224
print(f"Sender: {sender}")
225
# Output: sender@example.com
226
227
# Convert to bytes for transmission
228
message_bytes = flatten_message(message)
229
print(f"Message size: {len(message_bytes)} bytes")
230
231
# Parse and quote addresses
232
raw_address = "John Doe <john@example.com>"
233
parsed = parse_address(raw_address)
234
quoted = quote_address(parsed)
235
print(f"Raw: {raw_address}")
236
print(f"Parsed: {parsed}")
237
print(f"Quoted: {quoted}")
238
```
239
240
### ESMTP Extension Parsing
241
242
```python
243
from aiosmtplib.esmtp import parse_esmtp_extensions
244
245
# Example EHLO response from server
246
ehlo_response = """250-smtp.example.com Hello [192.168.1.100]
247
250-SIZE 35882577
248
250-8BITMIME
249
250-PIPELINING
250
250-CHUNKING
251
250-SMTPUTF8
252
250-AUTH PLAIN LOGIN CRAM-MD5
253
250-AUTH=PLAIN LOGIN CRAM-MD5
254
250 HELP"""
255
256
# Parse extensions and auth methods
257
extensions, auth_methods = parse_esmtp_extensions(ehlo_response)
258
259
print("Server extensions:")
260
for ext_name, ext_value in extensions.items():
261
print(f" {ext_name}: {ext_value}")
262
263
print(f"\nSupported auth methods: {auth_methods}")
264
265
# Check for specific extensions
266
if "PIPELINING" in extensions:
267
print("Server supports command pipelining")
268
269
if "SIZE" in extensions:
270
max_size = extensions["SIZE"]
271
print(f"Maximum message size: {max_size} bytes")
272
```
273
274
### Custom Authentication Implementation
275
276
```python
277
import asyncio
278
import aiosmtplib
279
import base64
280
281
async def custom_auth_example():
282
smtp = aiosmtplib.SMTP(hostname="localhost", port=1025)
283
284
try:
285
await smtp.connect()
286
287
# Check what auth methods server supports
288
auth_methods = smtp.supported_auth_methods
289
print(f"Server supports: {auth_methods}")
290
291
if "PLAIN" in auth_methods:
292
# Manual PLAIN authentication
293
credentials = aiosmtplib.auth_plain_encode("user", "pass")
294
auth_string = base64.b64encode(credentials).decode()
295
296
# Send AUTH command manually
297
response = await smtp.execute_command(f"AUTH PLAIN {auth_string}")
298
print(f"Manual PLAIN auth response: {response}")
299
300
elif "LOGIN" in auth_methods:
301
# Manual LOGIN authentication
302
username_b64, password_b64 = aiosmtplib.auth_login_encode("user", "pass")
303
304
# LOGIN authentication is interactive
305
response1 = await smtp.execute_command("AUTH LOGIN")
306
print(f"AUTH LOGIN response: {response1}")
307
308
response2 = await smtp.execute_command(username_b64.decode())
309
print(f"Username response: {response2}")
310
311
response3 = await smtp.execute_command(password_b64.decode())
312
print(f"Password response: {response3}")
313
314
finally:
315
smtp.close()
316
317
asyncio.run(custom_auth_example())
318
```
319
320
### Message Validation and Processing
321
322
```python
323
import aiosmtplib
324
from email.message import EmailMessage
325
import email.utils
326
327
def validate_and_process_message(message):
328
"""Validate and process email message before sending."""
329
330
# Extract and validate sender
331
sender = aiosmtplib.extract_sender(message)
332
if not sender:
333
raise ValueError("Message must have a From header")
334
335
# Validate sender format
336
try:
337
parsed_sender = aiosmtplib.parse_address(sender)
338
if "@" not in parsed_sender:
339
raise ValueError(f"Invalid sender address: {sender}")
340
except Exception as e:
341
raise ValueError(f"Cannot parse sender address: {e}")
342
343
# Extract and validate recipients
344
recipients = aiosmtplib.extract_recipients(message)
345
if not recipients:
346
raise ValueError("Message must have recipients (To, Cc, or Bcc)")
347
348
# Validate each recipient
349
valid_recipients = []
350
for recipient in recipients:
351
try:
352
parsed_recipient = aiosmtplib.parse_address(recipient)
353
if "@" not in parsed_recipient:
354
print(f"Warning: Skipping invalid recipient: {recipient}")
355
continue
356
valid_recipients.append(parsed_recipient)
357
except Exception as e:
358
print(f"Warning: Cannot parse recipient {recipient}: {e}")
359
360
if not valid_recipients:
361
raise ValueError("No valid recipients found")
362
363
# Add Message-ID if missing
364
if "Message-ID" not in message:
365
message["Message-ID"] = email.utils.make_msgid()
366
367
# Add Date if missing
368
if "Date" not in message:
369
message["Date"] = email.utils.formatdate(localtime=True)
370
371
return parsed_sender, valid_recipients
372
373
# Example usage
374
message = EmailMessage()
375
message["From"] = "John Doe <john@example.com>"
376
message["To"] = "recipient@example.com, invalid-email"
377
message["Subject"] = "Test Message"
378
message.set_content("This is a test message.")
379
380
try:
381
sender, recipients = validate_and_process_message(message)
382
print(f"Validated sender: {sender}")
383
print(f"Valid recipients: {recipients}")
384
print(f"Message-ID: {message['Message-ID']}")
385
print(f"Date: {message['Date']}")
386
except ValueError as e:
387
print(f"Message validation failed: {e}")
388
```
389
390
### Advanced Message Processing
391
392
```python
393
import aiosmtplib
394
from email.message import EmailMessage
395
from email.mime.multipart import MIMEMultipart
396
from email.mime.text import MIMEText
397
from email.mime.base import MIMEBase
398
import email.encoders
399
400
def create_complex_message():
401
"""Create a complex multipart message."""
402
403
# Create multipart message
404
msg = MIMEMultipart("alternative")
405
msg["From"] = "sender@example.com"
406
msg["To"] = "recipient@example.com"
407
msg["Subject"] = "Complex Message"
408
409
# Add text part
410
text_part = MIMEText("This is the plain text version.", "plain")
411
msg.attach(text_part)
412
413
# Add HTML part
414
html_part = MIMEText("<h1>This is the HTML version</h1>", "html")
415
msg.attach(html_part)
416
417
return msg
418
419
async def send_complex_message():
420
# Create complex message
421
message = create_complex_message()
422
423
# Process with utilities
424
sender = aiosmtplib.extract_sender(message)
425
recipients = aiosmtplib.extract_recipients(message)
426
message_bytes = aiosmtplib.flatten_message(message, utf8=True)
427
428
print(f"Sender: {sender}")
429
print(f"Recipients: {recipients}")
430
print(f"Message size: {len(message_bytes)} bytes")
431
432
# Send using SMTP client
433
smtp = aiosmtplib.SMTP(hostname="localhost", port=1025)
434
async with smtp:
435
response = await smtp.send_message(message)
436
print(f"Message sent: {response}")
437
438
asyncio.run(send_complex_message())
439
```
440
441
## Internal vs Public Utilities
442
443
### Public Utilities (Exported in __all__)
444
These functions are intended for public use:
445
- Authentication encoding functions
446
- Email message processing functions
447
- ESMTP extension parsing
448
- SMTPProtocol for advanced use cases
449
450
### Internal Utilities (Not Recommended for Direct Use)
451
Some utilities are for internal use but may be useful in specific scenarios:
452
- Low-level protocol functions
453
- Internal message parsing helpers
454
- Connection management utilities
455
456
When using any utility functions, prefer the high-level `send()` function or `SMTP` class for most use cases, and only use utilities when you need specific functionality not provided by the main API.