Advanced Application Framework for Python with a focus on Command Line Interfaces
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
Base interface for email functionality that defines the contract for mail operations.
class MailHandler:
"""
Mail handler interface for sending emails.
Provides methods for sending emails with support for templates,
attachments, and various email formats.
"""
def send(self, body: str, **kwargs: Any) -> bool:
"""
Send an email message.
Args:
body: Email message body
**kwargs: Additional email parameters (to, subject, from, etc.)
Returns:
True if email sent successfully, False otherwise
"""from cement import App, Controller, ex, init_defaults
CONFIG = init_defaults('myapp')
CONFIG['mail.smtp'] = {
'host': 'smtp.gmail.com',
'port': 587,
'tls': True,
'auth': True,
'username': 'your-email@gmail.com',
'password': 'your-app-password'
}
class EmailController(Controller):
class Meta:
label = 'email'
stacked_on = 'base'
stacked_type = 'nested'
@ex(
help='send email',
arguments=[
(['--to'], {'help': 'recipient email address', 'required': True}),
(['--subject'], {'help': 'email subject', 'required': True}),
(['--message'], {'help': 'email message', 'required': True})
]
)
def send(self):
"""Send a basic email."""
to_address = self.app.pargs.to
subject = self.app.pargs.subject
message = self.app.pargs.message
try:
success = self.app.mail.send(
body=message,
to=[to_address],
subject=subject,
from_addr=self.app.config.get('mail.smtp', 'username')
)
if success:
print(f'Email sent successfully to {to_address}')
else:
print('Failed to send email')
except Exception as e:
print(f'Error sending email: {e}')
class BaseController(Controller):
class Meta:
label = 'base'
class MyApp(App):
class Meta:
label = 'myapp'
base_controller = 'base'
extensions = ['smtp']
mail_handler = 'smtp'
config_defaults = CONFIG
handlers = [BaseController, EmailController]
with MyApp() as app:
app.run()
# Usage:
# myapp email send --to user@example.com --subject "Test" --message "Hello World"from cement import App, Controller, ex, init_defaults
CONFIG = init_defaults('myapp')
CONFIG['mail.smtp'] = {
'host': 'smtp.gmail.com',
'port': 587,
'tls': True,
'auth': True,
'username': 'notifications@mycompany.com',
'password': 'app-password'
}
CONFIG['template.jinja2'] = {
'template_dirs': ['./email_templates']
}
class NotificationController(Controller):
class Meta:
label = 'notify'
stacked_on = 'base'
stacked_type = 'nested'
@ex(
help='send welcome email',
arguments=[
(['--user'], {'help': 'username', 'required': True}),
(['--email'], {'help': 'user email address', 'required': True})
]
)
def welcome(self):
"""Send welcome email using HTML template."""
username = self.app.pargs.user
email = self.app.pargs.email
# Prepare template data
template_data = {
'username': username,
'company_name': 'MyCompany',
'login_url': 'https://mycompany.com/login',
'support_email': 'support@mycompany.com',
'features': [
'Access to dashboard',
'Real-time notifications',
'Advanced reporting',
'24/7 customer support'
]
}
try:
# Render email template
template = self.app.template.load('welcome_email.html')
html_body = self.app.template.render(template, template_data)
# Send HTML email
success = self.app.mail.send(
body=html_body,
to=[email],
subject=f'Welcome to {template_data["company_name"]}, {username}!',
from_addr=self.app.config.get('mail.smtp', 'username'),
content_type='html'
)
if success:
print(f'Welcome email sent to {email}')
else:
print('Failed to send welcome email')
except Exception as e:
print(f'Error sending welcome email: {e}')
@ex(
help='send report email',
arguments=[
(['--recipients'], {'help': 'comma-separated email addresses', 'required': True}),
(['--report-type'], {'help': 'type of report', 'required': True})
]
)
def report(self):
"""Send report email with data."""
recipients = [email.strip() for email in self.app.pargs.recipients.split(',')]
report_type = self.app.pargs.report_type
# Generate report data (example)
report_data = {
'report_type': report_type,
'generated_date': '2023-01-15',
'metrics': [
{'name': 'Total Users', 'value': 1250, 'change': '+5%'},
{'name': 'Active Sessions', 'value': 89, 'change': '+12%'},
{'name': 'Revenue', 'value': '$45,230', 'change': '+8%'}
],
'summary': 'Overall performance improved this month with increased user engagement.'
}
try:
# Render report template
template = self.app.template.load('report_email.html')
html_body = self.app.template.render(template, report_data)
# Send to multiple recipients
success = self.app.mail.send(
body=html_body,
to=recipients,
subject=f'{report_type} Report - {report_data["generated_date"]}',
from_addr=self.app.config.get('mail.smtp', 'username'),
content_type='html'
)
if success:
print(f'Report sent to {len(recipients)} recipients')
else:
print('Failed to send report email')
except Exception as e:
print(f'Error sending report: {e}')
class BaseController(Controller):
class Meta:
label = 'base'
class MyApp(App):
class Meta:
label = 'myapp'
base_controller = 'base'
extensions = ['smtp', 'jinja2']
mail_handler = 'smtp'
template_handler = 'jinja2'
config_defaults = CONFIG
handlers = [BaseController, NotificationController]
with MyApp() as app:
app.run()
# Example template: email_templates/welcome_email.html
"""
<!DOCTYPE html>
<html>
<head>
<title>Welcome to {{ company_name }}</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.header { background: #007bff; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; }
.features { background: #f8f9fa; padding: 15px; margin: 20px 0; }
.footer { background: #6c757d; color: white; padding: 15px; text-align: center; }
.btn { background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; }
</style>
</head>
<body>
<div class="header">
<h1>Welcome to {{ company_name }}, {{ username }}!</h1>
</div>
<div class="content">
<p>Thank you for joining {{ company_name }}. We're excited to have you on board!</p>
<div class="features">
<h3>Your account includes:</h3>
<ul>
{% for feature in features %}
<li>{{ feature }}</li>
{% endfor %}
</ul>
</div>
<p><a href="{{ login_url }}" class="btn">Login to Your Account</a></p>
<p>If you have any questions, please contact us at {{ support_email }}</p>
</div>
<div class="footer">
<p>© 2023 {{ company_name }}. All rights reserved.</p>
</div>
</body>
</html>
"""
# Usage:
# myapp notify welcome --user johndoe --email john@example.com
# myapp notify report --recipients "manager@company.com,ceo@company.com" --report-type "Monthly"from cement import App, Controller, ex, init_defaults
import os
import tempfile
import csv
CONFIG = init_defaults('myapp')
CONFIG['mail.smtp'] = {
'host': 'smtp.company.com',
'port': 587,
'tls': True,
'auth': True,
'username': 'reports@company.com',
'password': 'secure-password'
}
class ReportController(Controller):
class Meta:
label = 'report'
stacked_on = 'base'
stacked_type = 'nested'
def generate_csv_report(self, report_type):
"""Generate CSV report file."""
# Sample data
if report_type == 'users':
data = [
{'ID': 1, 'Name': 'Alice Johnson', 'Email': 'alice@example.com', 'Status': 'Active'},
{'ID': 2, 'Name': 'Bob Smith', 'Email': 'bob@example.com', 'Status': 'Inactive'},
{'ID': 3, 'Name': 'Charlie Brown', 'Email': 'charlie@example.com', 'Status': 'Active'}
]
elif report_type == 'sales':
data = [
{'Date': '2023-01-01', 'Product': 'Widget A', 'Amount': 1500.00, 'Customer': 'Acme Corp'},
{'Date': '2023-01-02', 'Product': 'Widget B', 'Amount': 2300.00, 'Customer': 'Beta LLC'},
{'Date': '2023-01-03', 'Product': 'Widget C', 'Amount': 1800.00, 'Customer': 'Gamma Inc'}
]
else:
data = []
# Create temporary CSV file
temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.csv')
if data:
writer = csv.DictWriter(temp_file, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)
temp_file.close()
return temp_file.name
@ex(
help='email report with attachment',
arguments=[
(['--recipients'], {'help': 'comma-separated email addresses', 'required': True}),
(['--report-type'], {
'choices': ['users', 'sales'],
'help': 'type of report to generate',
'required': True
})
]
)
def email(self):
"""Email report with CSV attachment."""
recipients = [email.strip() for email in self.app.pargs.recipients.split(',')]
report_type = self.app.pargs.report_type
try:
# Generate CSV report
csv_file = self.generate_csv_report(report_type)
print(f'Generated {report_type} report: {csv_file}')
# Email body
email_body = f"""
Dear Team,
Please find attached the {report_type} report generated on {datetime.date.today()}.
The report contains the latest data and metrics for your review.
Best regards,
Automated Reporting System
""".strip()
# Send email with attachment
success = self.app.mail.send(
body=email_body,
to=recipients,
subject=f'{report_type.title()} Report - {datetime.date.today()}',
from_addr=self.app.config.get('mail.smtp', 'username'),
attachments=[{
'filename': f'{report_type}_report_{datetime.date.today()}.csv',
'path': csv_file
}]
)
if success:
print(f'Report emailed to {len(recipients)} recipients')
else:
print('Failed to send report email')
# Cleanup temporary file
os.unlink(csv_file)
except Exception as e:
print(f'Error generating/sending report: {e}')
class BaseController(Controller):
class Meta:
label = 'base'
class MyApp(App):
class Meta:
label = 'myapp'
base_controller = 'base'
extensions = ['smtp']
mail_handler = 'smtp'
config_defaults = CONFIG
handlers = [BaseController, ReportController]
with MyApp() as app:
app.run()
# Usage:
# myapp report email --recipients "manager@company.com,analyst@company.com" --report-type usersfrom cement import App, Controller, ex, init_defaults
import time
import threading
CONFIG = init_defaults('myapp')
CONFIG['mail.smtp'] = {
'host': 'smtp.mailservice.com',
'port': 587,
'tls': True,
'auth': True,
'username': 'noreply@company.com',
'password': 'bulk-email-password',
'rate_limit': 10, # emails per minute
'batch_size': 50 # emails per batch
}
class BulkEmailController(Controller):
class Meta:
label = 'bulk'
stacked_on = 'base'
stacked_type = 'nested'
def send_bulk_emails(self, recipients, subject, body, rate_limit=10):
"""Send emails to multiple recipients with rate limiting."""
sent_count = 0
failed_count = 0
print(f'Sending emails to {len(recipients)} recipients')
print(f'Rate limit: {rate_limit} emails per minute')
for i, recipient in enumerate(recipients):
try:
success = self.app.mail.send(
body=body,
to=[recipient],
subject=subject,
from_addr=self.app.config.get('mail.smtp', 'username')
)
if success:
sent_count += 1
print(f'✓ Sent to {recipient} ({i+1}/{len(recipients)})')
else:
failed_count += 1
print(f'✗ Failed to send to {recipient}')
# Rate limiting
if (i + 1) % rate_limit == 0:
print(f'Rate limit reached, waiting 60 seconds...')
time.sleep(60)
else:
time.sleep(6) # 10 emails per minute = 6 seconds between emails
except Exception as e:
failed_count += 1
print(f'✗ Error sending to {recipient}: {e}')
print(f'\nBulk email completed:')
print(f' Sent: {sent_count}')
print(f' Failed: {failed_count}')
print(f' Total: {len(recipients)}')
@ex(
help='send newsletter',
arguments=[
(['--subscriber-file'], {
'help': 'file containing subscriber emails (one per line)',
'required': True
}),
(['--subject'], {'help': 'newsletter subject', 'required': True}),
(['--template'], {'help': 'newsletter template file', 'required': True})
]
)
def newsletter(self):
"""Send newsletter to subscribers."""
subscriber_file = self.app.pargs.subscriber_file
subject = self.app.pargs.subject
template_file = self.app.pargs.template
try:
# Read subscriber list
with open(subscriber_file, 'r') as f:
subscribers = [line.strip() for line in f if line.strip()]
print(f'Loaded {len(subscribers)} subscribers')
# Read newsletter template
with open(template_file, 'r') as f:
newsletter_body = f.read()
# Get rate limit from config
rate_limit = self.app.config.get('mail.smtp', 'rate_limit')
# Send bulk emails
self.send_bulk_emails(subscribers, subject, newsletter_body, rate_limit)
except FileNotFoundError as e:
print(f'File not found: {e}')
except Exception as e:
print(f'Error sending newsletter: {e}')
@ex(
help='send promotional email',
arguments=[
(['--segment'], {
'choices': ['all', 'premium', 'basic', 'trial'],
'help': 'customer segment to target',
'default': 'all'
}),
(['--subject'], {'help': 'promotion subject', 'required': True}),
(['--message'], {'help': 'promotion message', 'required': True})
]
)
def promotion(self):
"""Send promotional email to customer segments."""
segment = self.app.pargs.segment
subject = self.app.pargs.subject
message = self.app.pargs.message
# Sample customer data (would come from database)
customers = {
'premium': ['premium1@example.com', 'premium2@example.com'],
'basic': ['basic1@example.com', 'basic2@example.com'],
'trial': ['trial1@example.com', 'trial2@example.com']
}
if segment == 'all':
recipients = []
for segment_emails in customers.values():
recipients.extend(segment_emails)
else:
recipients = customers.get(segment, [])
if not recipients:
print(f'No recipients found for segment: {segment}')
return
print(f'Sending promotion to {segment} segment ({len(recipients)} recipients)')
# Personalize message
personalized_message = f"""
{message}
---
This email was sent to customers in the '{segment}' segment.
To unsubscribe, reply with 'UNSUBSCRIBE' in the subject line.
""".strip()
# Send bulk emails with rate limiting
rate_limit = self.app.config.get('mail.smtp', 'rate_limit')
self.send_bulk_emails(recipients, subject, personalized_message, rate_limit)
class BaseController(Controller):
class Meta:
label = 'base'
class MyApp(App):
class Meta:
label = 'myapp'
base_controller = 'base'
extensions = ['smtp']
mail_handler = 'smtp'
config_defaults = CONFIG
handlers = [BaseController, BulkEmailController]
with MyApp() as app:
app.run()
# Usage:
# myapp bulk newsletter --subscriber-file subscribers.txt --subject "Monthly Newsletter" --template newsletter.html
# myapp bulk promotion --segment premium --subject "Exclusive Offer" --message "Special discount for premium customers"Install with Tessl CLI
npx tessl i tessl/pypi-cement