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>© 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
```