or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

arguments.mdcaching.mdconfiguration.mdcontrollers.mdextensions.mdfoundation.mdhooks.mdindex.mdinterface-handler.mdlogging.mdmail.mdoutput.mdplugins.mdtemplates.mdutilities.md

mail.mddocs/

0

# Mail System

1

2

The mail system provides email functionality framework supporting SMTP-based email sending with template integration and attachment support. It enables applications to send notifications, reports, and other communications via email.

3

4

## Capabilities

5

6

### Mail Handler Interface

7

8

Base interface for email functionality that defines the contract for mail operations.

9

10

```python { .api }

11

class MailHandler:

12

"""

13

Mail handler interface for sending emails.

14

15

Provides methods for sending emails with support for templates,

16

attachments, and various email formats.

17

"""

18

19

def send(self, body: str, **kwargs: Any) -> bool:

20

"""

21

Send an email message.

22

23

Args:

24

body: Email message body

25

**kwargs: Additional email parameters (to, subject, from, etc.)

26

27

Returns:

28

True if email sent successfully, False otherwise

29

"""

30

```

31

32

## Usage Examples

33

34

### Basic Email Sending

35

36

```python

37

from cement import App, Controller, ex, init_defaults

38

39

CONFIG = init_defaults('myapp')

40

CONFIG['mail.smtp'] = {

41

'host': 'smtp.gmail.com',

42

'port': 587,

43

'tls': True,

44

'auth': True,

45

'username': 'your-email@gmail.com',

46

'password': 'your-app-password'

47

}

48

49

class EmailController(Controller):

50

class Meta:

51

label = 'email'

52

stacked_on = 'base'

53

stacked_type = 'nested'

54

55

@ex(

56

help='send email',

57

arguments=[

58

(['--to'], {'help': 'recipient email address', 'required': True}),

59

(['--subject'], {'help': 'email subject', 'required': True}),

60

(['--message'], {'help': 'email message', 'required': True})

61

]

62

)

63

def send(self):

64

"""Send a basic email."""

65

to_address = self.app.pargs.to

66

subject = self.app.pargs.subject

67

message = self.app.pargs.message

68

69

try:

70

success = self.app.mail.send(

71

body=message,

72

to=[to_address],

73

subject=subject,

74

from_addr=self.app.config.get('mail.smtp', 'username')

75

)

76

77

if success:

78

print(f'Email sent successfully to {to_address}')

79

else:

80

print('Failed to send email')

81

82

except Exception as e:

83

print(f'Error sending email: {e}')

84

85

class BaseController(Controller):

86

class Meta:

87

label = 'base'

88

89

class MyApp(App):

90

class Meta:

91

label = 'myapp'

92

base_controller = 'base'

93

extensions = ['smtp']

94

mail_handler = 'smtp'

95

config_defaults = CONFIG

96

handlers = [BaseController, EmailController]

97

98

with MyApp() as app:

99

app.run()

100

101

# Usage:

102

# myapp email send --to user@example.com --subject "Test" --message "Hello World"

103

```

104

105

### HTML Email with Templates

106

107

```python

108

from cement import App, Controller, ex, init_defaults

109

110

CONFIG = init_defaults('myapp')

111

CONFIG['mail.smtp'] = {

112

'host': 'smtp.gmail.com',

113

'port': 587,

114

'tls': True,

115

'auth': True,

116

'username': 'notifications@mycompany.com',

117

'password': 'app-password'

118

}

119

120

CONFIG['template.jinja2'] = {

121

'template_dirs': ['./email_templates']

122

}

123

124

class NotificationController(Controller):

125

class Meta:

126

label = 'notify'

127

stacked_on = 'base'

128

stacked_type = 'nested'

129

130

@ex(

131

help='send welcome email',

132

arguments=[

133

(['--user'], {'help': 'username', 'required': True}),

134

(['--email'], {'help': 'user email address', 'required': True})

135

]

136

)

137

def welcome(self):

138

"""Send welcome email using HTML template."""

139

username = self.app.pargs.user

140

email = self.app.pargs.email

141

142

# Prepare template data

143

template_data = {

144

'username': username,

145

'company_name': 'MyCompany',

146

'login_url': 'https://mycompany.com/login',

147

'support_email': 'support@mycompany.com',

148

'features': [

149

'Access to dashboard',

150

'Real-time notifications',

151

'Advanced reporting',

152

'24/7 customer support'

153

]

154

}

155

156

try:

157

# Render email template

158

template = self.app.template.load('welcome_email.html')

159

html_body = self.app.template.render(template, template_data)

160

161

# Send HTML email

162

success = self.app.mail.send(

163

body=html_body,

164

to=[email],

165

subject=f'Welcome to {template_data["company_name"]}, {username}!',

166

from_addr=self.app.config.get('mail.smtp', 'username'),

167

content_type='html'

168

)

169

170

if success:

171

print(f'Welcome email sent to {email}')

172

else:

173

print('Failed to send welcome email')

174

175

except Exception as e:

176

print(f'Error sending welcome email: {e}')

177

178

@ex(

179

help='send report email',

180

arguments=[

181

(['--recipients'], {'help': 'comma-separated email addresses', 'required': True}),

182

(['--report-type'], {'help': 'type of report', 'required': True})

183

]

184

)

185

def report(self):

186

"""Send report email with data."""

187

recipients = [email.strip() for email in self.app.pargs.recipients.split(',')]

188

report_type = self.app.pargs.report_type

189

190

# Generate report data (example)

191

report_data = {

192

'report_type': report_type,

193

'generated_date': '2023-01-15',

194

'metrics': [

195

{'name': 'Total Users', 'value': 1250, 'change': '+5%'},

196

{'name': 'Active Sessions', 'value': 89, 'change': '+12%'},

197

{'name': 'Revenue', 'value': '$45,230', 'change': '+8%'}

198

],

199

'summary': 'Overall performance improved this month with increased user engagement.'

200

}

201

202

try:

203

# Render report template

204

template = self.app.template.load('report_email.html')

205

html_body = self.app.template.render(template, report_data)

206

207

# Send to multiple recipients

208

success = self.app.mail.send(

209

body=html_body,

210

to=recipients,

211

subject=f'{report_type} Report - {report_data["generated_date"]}',

212

from_addr=self.app.config.get('mail.smtp', 'username'),

213

content_type='html'

214

)

215

216

if success:

217

print(f'Report sent to {len(recipients)} recipients')

218

else:

219

print('Failed to send report email')

220

221

except Exception as e:

222

print(f'Error sending report: {e}')

223

224

class BaseController(Controller):

225

class Meta:

226

label = 'base'

227

228

class MyApp(App):

229

class Meta:

230

label = 'myapp'

231

base_controller = 'base'

232

extensions = ['smtp', 'jinja2']

233

mail_handler = 'smtp'

234

template_handler = 'jinja2'

235

config_defaults = CONFIG

236

handlers = [BaseController, NotificationController]

237

238

with MyApp() as app:

239

app.run()

240

241

# Example template: email_templates/welcome_email.html

242

"""

243

<!DOCTYPE html>

244

<html>

245

<head>

246

<title>Welcome to {{ company_name }}</title>

247

<style>

248

body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }

249

.header { background: #007bff; color: white; padding: 20px; text-align: center; }

250

.content { padding: 20px; }

251

.features { background: #f8f9fa; padding: 15px; margin: 20px 0; }

252

.footer { background: #6c757d; color: white; padding: 15px; text-align: center; }

253

.btn { background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; }

254

</style>

255

</head>

256

<body>

257

<div class="header">

258

<h1>Welcome to {{ company_name }}, {{ username }}!</h1>

259

</div>

260

261

<div class="content">

262

<p>Thank you for joining {{ company_name }}. We're excited to have you on board!</p>

263

264

<div class="features">

265

<h3>Your account includes:</h3>

266

<ul>

267

{% for feature in features %}

268

<li>{{ feature }}</li>

269

{% endfor %}

270

</ul>

271

</div>

272

273

<p><a href="{{ login_url }}" class="btn">Login to Your Account</a></p>

274

275

<p>If you have any questions, please contact us at {{ support_email }}</p>

276

</div>

277

278

<div class="footer">

279

<p>&copy; 2023 {{ company_name }}. All rights reserved.</p>

280

</div>

281

</body>

282

</html>

283

"""

284

285

# Usage:

286

# myapp notify welcome --user johndoe --email john@example.com

287

# myapp notify report --recipients "manager@company.com,ceo@company.com" --report-type "Monthly"

288

```

289

290

### Email with Attachments

291

292

```python

293

from cement import App, Controller, ex, init_defaults

294

import os

295

import tempfile

296

import csv

297

298

CONFIG = init_defaults('myapp')

299

CONFIG['mail.smtp'] = {

300

'host': 'smtp.company.com',

301

'port': 587,

302

'tls': True,

303

'auth': True,

304

'username': 'reports@company.com',

305

'password': 'secure-password'

306

}

307

308

class ReportController(Controller):

309

class Meta:

310

label = 'report'

311

stacked_on = 'base'

312

stacked_type = 'nested'

313

314

def generate_csv_report(self, report_type):

315

"""Generate CSV report file."""

316

# Sample data

317

if report_type == 'users':

318

data = [

319

{'ID': 1, 'Name': 'Alice Johnson', 'Email': 'alice@example.com', 'Status': 'Active'},

320

{'ID': 2, 'Name': 'Bob Smith', 'Email': 'bob@example.com', 'Status': 'Inactive'},

321

{'ID': 3, 'Name': 'Charlie Brown', 'Email': 'charlie@example.com', 'Status': 'Active'}

322

]

323

elif report_type == 'sales':

324

data = [

325

{'Date': '2023-01-01', 'Product': 'Widget A', 'Amount': 1500.00, 'Customer': 'Acme Corp'},

326

{'Date': '2023-01-02', 'Product': 'Widget B', 'Amount': 2300.00, 'Customer': 'Beta LLC'},

327

{'Date': '2023-01-03', 'Product': 'Widget C', 'Amount': 1800.00, 'Customer': 'Gamma Inc'}

328

]

329

else:

330

data = []

331

332

# Create temporary CSV file

333

temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.csv')

334

335

if data:

336

writer = csv.DictWriter(temp_file, fieldnames=data[0].keys())

337

writer.writeheader()

338

writer.writerows(data)

339

340

temp_file.close()

341

return temp_file.name

342

343

@ex(

344

help='email report with attachment',

345

arguments=[

346

(['--recipients'], {'help': 'comma-separated email addresses', 'required': True}),

347

(['--report-type'], {

348

'choices': ['users', 'sales'],

349

'help': 'type of report to generate',

350

'required': True

351

})

352

]

353

)

354

def email(self):

355

"""Email report with CSV attachment."""

356

recipients = [email.strip() for email in self.app.pargs.recipients.split(',')]

357

report_type = self.app.pargs.report_type

358

359

try:

360

# Generate CSV report

361

csv_file = self.generate_csv_report(report_type)

362

print(f'Generated {report_type} report: {csv_file}')

363

364

# Email body

365

email_body = f"""

366

Dear Team,

367

368

Please find attached the {report_type} report generated on {datetime.date.today()}.

369

370

The report contains the latest data and metrics for your review.

371

372

Best regards,

373

Automated Reporting System

374

""".strip()

375

376

# Send email with attachment

377

success = self.app.mail.send(

378

body=email_body,

379

to=recipients,

380

subject=f'{report_type.title()} Report - {datetime.date.today()}',

381

from_addr=self.app.config.get('mail.smtp', 'username'),

382

attachments=[{

383

'filename': f'{report_type}_report_{datetime.date.today()}.csv',

384

'path': csv_file

385

}]

386

)

387

388

if success:

389

print(f'Report emailed to {len(recipients)} recipients')

390

else:

391

print('Failed to send report email')

392

393

# Cleanup temporary file

394

os.unlink(csv_file)

395

396

except Exception as e:

397

print(f'Error generating/sending report: {e}')

398

399

class BaseController(Controller):

400

class Meta:

401

label = 'base'

402

403

class MyApp(App):

404

class Meta:

405

label = 'myapp'

406

base_controller = 'base'

407

extensions = ['smtp']

408

mail_handler = 'smtp'

409

config_defaults = CONFIG

410

handlers = [BaseController, ReportController]

411

412

with MyApp() as app:

413

app.run()

414

415

# Usage:

416

# myapp report email --recipients "manager@company.com,analyst@company.com" --report-type users

417

```

418

419

### Bulk Email with Rate Limiting

420

421

```python

422

from cement import App, Controller, ex, init_defaults

423

import time

424

import threading

425

426

CONFIG = init_defaults('myapp')

427

CONFIG['mail.smtp'] = {

428

'host': 'smtp.mailservice.com',

429

'port': 587,

430

'tls': True,

431

'auth': True,

432

'username': 'noreply@company.com',

433

'password': 'bulk-email-password',

434

'rate_limit': 10, # emails per minute

435

'batch_size': 50 # emails per batch

436

}

437

438

class BulkEmailController(Controller):

439

class Meta:

440

label = 'bulk'

441

stacked_on = 'base'

442

stacked_type = 'nested'

443

444

def send_bulk_emails(self, recipients, subject, body, rate_limit=10):

445

"""Send emails to multiple recipients with rate limiting."""

446

sent_count = 0

447

failed_count = 0

448

449

print(f'Sending emails to {len(recipients)} recipients')

450

print(f'Rate limit: {rate_limit} emails per minute')

451

452

for i, recipient in enumerate(recipients):

453

try:

454

success = self.app.mail.send(

455

body=body,

456

to=[recipient],

457

subject=subject,

458

from_addr=self.app.config.get('mail.smtp', 'username')

459

)

460

461

if success:

462

sent_count += 1

463

print(f'✓ Sent to {recipient} ({i+1}/{len(recipients)})')

464

else:

465

failed_count += 1

466

print(f'✗ Failed to send to {recipient}')

467

468

# Rate limiting

469

if (i + 1) % rate_limit == 0:

470

print(f'Rate limit reached, waiting 60 seconds...')

471

time.sleep(60)

472

else:

473

time.sleep(6) # 10 emails per minute = 6 seconds between emails

474

475

except Exception as e:

476

failed_count += 1

477

print(f'✗ Error sending to {recipient}: {e}')

478

479

print(f'\nBulk email completed:')

480

print(f' Sent: {sent_count}')

481

print(f' Failed: {failed_count}')

482

print(f' Total: {len(recipients)}')

483

484

@ex(

485

help='send newsletter',

486

arguments=[

487

(['--subscriber-file'], {

488

'help': 'file containing subscriber emails (one per line)',

489

'required': True

490

}),

491

(['--subject'], {'help': 'newsletter subject', 'required': True}),

492

(['--template'], {'help': 'newsletter template file', 'required': True})

493

]

494

)

495

def newsletter(self):

496

"""Send newsletter to subscribers."""

497

subscriber_file = self.app.pargs.subscriber_file

498

subject = self.app.pargs.subject

499

template_file = self.app.pargs.template

500

501

try:

502

# Read subscriber list

503

with open(subscriber_file, 'r') as f:

504

subscribers = [line.strip() for line in f if line.strip()]

505

506

print(f'Loaded {len(subscribers)} subscribers')

507

508

# Read newsletter template

509

with open(template_file, 'r') as f:

510

newsletter_body = f.read()

511

512

# Get rate limit from config

513

rate_limit = self.app.config.get('mail.smtp', 'rate_limit')

514

515

# Send bulk emails

516

self.send_bulk_emails(subscribers, subject, newsletter_body, rate_limit)

517

518

except FileNotFoundError as e:

519

print(f'File not found: {e}')

520

except Exception as e:

521

print(f'Error sending newsletter: {e}')

522

523

@ex(

524

help='send promotional email',

525

arguments=[

526

(['--segment'], {

527

'choices': ['all', 'premium', 'basic', 'trial'],

528

'help': 'customer segment to target',

529

'default': 'all'

530

}),

531

(['--subject'], {'help': 'promotion subject', 'required': True}),

532

(['--message'], {'help': 'promotion message', 'required': True})

533

]

534

)

535

def promotion(self):

536

"""Send promotional email to customer segments."""

537

segment = self.app.pargs.segment

538

subject = self.app.pargs.subject

539

message = self.app.pargs.message

540

541

# Sample customer data (would come from database)

542

customers = {

543

'premium': ['premium1@example.com', 'premium2@example.com'],

544

'basic': ['basic1@example.com', 'basic2@example.com'],

545

'trial': ['trial1@example.com', 'trial2@example.com']

546

}

547

548

if segment == 'all':

549

recipients = []

550

for segment_emails in customers.values():

551

recipients.extend(segment_emails)

552

else:

553

recipients = customers.get(segment, [])

554

555

if not recipients:

556

print(f'No recipients found for segment: {segment}')

557

return

558

559

print(f'Sending promotion to {segment} segment ({len(recipients)} recipients)')

560

561

# Personalize message

562

personalized_message = f"""

563

{message}

564

565

---

566

This email was sent to customers in the '{segment}' segment.

567

To unsubscribe, reply with 'UNSUBSCRIBE' in the subject line.

568

""".strip()

569

570

# Send bulk emails with rate limiting

571

rate_limit = self.app.config.get('mail.smtp', 'rate_limit')

572

self.send_bulk_emails(recipients, subject, personalized_message, rate_limit)

573

574

class BaseController(Controller):

575

class Meta:

576

label = 'base'

577

578

class MyApp(App):

579

class Meta:

580

label = 'myapp'

581

base_controller = 'base'

582

extensions = ['smtp']

583

mail_handler = 'smtp'

584

config_defaults = CONFIG

585

handlers = [BaseController, BulkEmailController]

586

587

with MyApp() as app:

588

app.run()

589

590

# Usage:

591

# myapp bulk newsletter --subscriber-file subscribers.txt --subject "Monthly Newsletter" --template newsletter.html

592

# myapp bulk promotion --segment premium --subject "Exclusive Offer" --message "Special discount for premium customers"

593

```