CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cement

Advanced Application Framework for Python with a focus on Command Line Interfaces

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

mail.mddocs/

Mail System

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.

Capabilities

Mail Handler Interface

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
        """

Usage Examples

Basic Email Sending

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"

HTML Email with Templates

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>&copy; 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"

Email with Attachments

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 users

Bulk Email with Rate Limiting

from 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

docs

arguments.md

caching.md

configuration.md

controllers.md

extensions.md

foundation.md

hooks.md

index.md

interface-handler.md

logging.md

mail.md

output.md

plugins.md

templates.md

utilities.md

tile.json