or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

exceptions.mdhigh-level-api.mdindex.mdsmtp-client.mdutilities.md

smtp-client.mddocs/

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

```