- Add SMTP fields to TicketEmailSettings model (host, port, TLS/SSL, credentials, from email/name) - Update serializers with SMTP fields and is_smtp_configured flag - Add TicketEmailTestSmtpView for testing SMTP connections - Update frontend API types and hooks for SMTP settings - Add collapsible IMAP and SMTP configuration sections with "Configured" badges - Fix TypeScript errors in mockData.ts (missing required fields, type mismatches) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
16 KiB
Resource-Free Scheduler & Plugin System
Overview
The Resource-Free Scheduler is a comprehensive system for running automated tasks on schedules without requiring resource allocation. It's separate from the customer-facing Event/Appointment system and designed for internal automation tasks.
Key Features
- Plugin-Based Architecture: Extensible system for creating custom automated tasks
- Multiple Schedule Types: Cron expressions, fixed intervals, and one-time executions
- Celery Integration: Asynchronous task execution with retry logic
- Execution Logging: Complete audit trail of all task executions
- Multi-Tenant Aware: Works seamlessly with django-tenants
- Built-in Plugins: Common tasks ready to use out of the box
Architecture
┌─────────────────┐
│ ScheduledTask │ (Model: Configuration for recurring tasks)
│ - plugin_name │
│ - schedule_type │
│ - config │
└────────┬────────┘
│
├─> Plugin Registry ──> BasePlugin ──> Custom Plugins
│ │
│ ├─> SendEmailPlugin
│ ├─> DailyReportPlugin
│ ├─> CleanupPlugin
│ └─> WebhookPlugin
│
└─> Celery Tasks ──> execute_scheduled_task()
│
└─> TaskExecutionLog (Model: Execution history)
Core Components
1. Models (schedule/models.py)
ScheduledTask
Stores configuration for scheduled tasks.
Fields:
name- Human-readable task namedescription- What the task doesplugin_name- Which plugin to executeplugin_config- JSON configuration for the pluginschedule_type- CRON, INTERVAL, or ONE_TIMEcron_expression- Cron syntax (e.g., "0 0 * * *")interval_minutes- Run every N minutesrun_at- Specific datetime for one-time tasksstatus- ACTIVE, PAUSED, or DISABLEDnext_run_at- Calculated next execution timelast_run_at- When last executedlast_run_status- success/failedlast_run_result- JSON result from last execution
TaskExecutionLog
Audit log of all task executions.
Fields:
scheduled_task- Reference to the taskstarted_at- Execution start timecompleted_at- Execution end timestatus- SUCCESS, FAILED, or SKIPPEDresult- JSON result from pluginerror_message- Error details if failedexecution_time_ms- Duration in milliseconds
2. Plugin System (schedule/plugins.py)
BasePlugin
Abstract base class for all plugins.
Required Attributes:
name- Unique identifier (snake_case)display_name- Human-readable namedescription- What the plugin doescategory- For organization (e.g., "communication", "reporting")
Required Methods:
execute(context)- Main task logic
Optional Methods:
validate_config()- Validate plugin configurationcan_execute(context)- Pre-execution checkson_success(result)- Post-success callbackon_failure(error)- Error handling callback
Plugin Registry
Global registry for managing plugins.
Methods:
register(plugin_class)- Register a pluginget(plugin_name)- Get plugin class by nameget_instance(plugin_name, config)- Create plugin instancelist_all()- List all plugins with metadatalist_by_category()- Group plugins by category
Template Variables (schedule/template_parser.py)
Template variables allow plugins to define configurable fields that users can fill in via the UI.
Variable Format:
{{PROMPT:variable_name|description}}
{{PROMPT:variable_name|description|default_value}}
{{PROMPT:variable_name|description|default_value|type}}
{{PROMPT:variable_name|description||type}} (no default, explicit type)
Supported Types:
| Type | Description | UI Component |
|---|---|---|
text |
Single-line text input | Text input |
textarea |
Multi-line text input | Textarea |
email |
Email address | Email input with validation |
number |
Numeric value | Number input |
url |
URL/webhook endpoint | URL input with validation |
email_template |
Email template selector | Dropdown of email templates |
Type Inference: If no explicit type is provided, the parser infers type from the variable name:
- Names containing
email→emailtype - Names containing
count,days,hours,amount→numbertype - Names containing
url,webhook,endpoint→urltype - Names containing
message,body,content→textareatype - Default →
texttype
Example - Email Template Variable:
# In your plugin script, reference an email template:
template_id = {{PROMPT:confirmation_template|Email template for confirmations||email_template}}
# The UI will show a dropdown of all available email templates
# The user selects a template, and the template ID is stored in the config
Using Email Templates in Plugins:
from schedule.models import EmailTemplate
class MyNotificationPlugin(BasePlugin):
def execute(self, context):
template_id = self.config.get('confirmation_template')
if template_id:
template = EmailTemplate.objects.get(id=template_id)
subject, html, text = template.render({
'BUSINESS_NAME': context['business'].name,
'CUSTOMER_NAME': customer.name,
# ... other variables
})
# Send the email using the rendered template
send_email(recipient, subject, html, text)
Insertion Codes (for use within templates):
These codes are replaced at runtime with actual values:
{{BUSINESS_NAME}}- Business name{{BUSINESS_EMAIL}}- Business contact email{{BUSINESS_PHONE}}- Business phone number{{CUSTOMER_NAME}}- Customer's name{{CUSTOMER_EMAIL}}- Customer's email{{APPOINTMENT_TIME}}- Appointment date/time{{APPOINTMENT_DATE}}- Appointment date only{{APPOINTMENT_SERVICE}}- Service name{{TODAY}}- Today's date{{NOW}}- Current date and time
3. Celery Tasks (schedule/tasks.py)
execute_scheduled_task(scheduled_task_id)
Main task executor. Runs the plugin, logs results, updates next run time.
Features:
- Automatic retry with exponential backoff
- Multi-tenant context preservation
- Pre-execution validation
- Comprehensive error handling
- Automatic next-run calculation
check_and_schedule_tasks()
Background task that finds due tasks and queues them for execution.
Built-in Plugins
1. SendEmailPlugin
Send custom emails to recipients.
Config:
{
"recipients": ["user@example.com"],
"subject": "Subject line",
"message": "Email body",
"from_email": "sender@example.com" // optional
}
2. CleanupOldEventsPlugin
Delete old completed/canceled events.
Config:
{
"days_old": 90,
"statuses": ["COMPLETED", "CANCELED"],
"dry_run": false
}
3. DailyReportPlugin
Send daily business summary reports.
Config:
{
"recipients": ["manager@example.com"],
"include_upcoming": true,
"include_completed": true
}
4. AppointmentReminderPlugin
Send appointment reminders to customers.
Config:
{
"hours_before": 24,
"method": "email", // or "sms", "both"
"message_template": "Optional custom template"
}
5. BackupDatabasePlugin
Create database backups.
Config:
{
"backup_location": "/path/to/backups",
"compress": true
}
6. WebhookPlugin
Call external webhook URLs.
Config:
{
"url": "https://example.com/webhook",
"method": "POST",
"headers": {"Authorization": "Bearer token"},
"payload": {"key": "value"}
}
API Endpoints
Scheduled Tasks
List tasks:
GET /api/scheduled-tasks/
Create task:
POST /api/scheduled-tasks/
{
"name": "Daily Cleanup",
"description": "Clean up old events",
"plugin_name": "cleanup_old_events",
"plugin_config": {"days_old": 90},
"schedule_type": "CRON",
"cron_expression": "0 0 * * *",
"status": "ACTIVE"
}
Update task:
PATCH /api/scheduled-tasks/{id}/
Pause task:
POST /api/scheduled-tasks/{id}/pause/
Resume task:
POST /api/scheduled-tasks/{id}/resume/
Manually execute:
POST /api/scheduled-tasks/{id}/execute/
Get execution logs:
GET /api/scheduled-tasks/{id}/logs/?limit=20&offset=0
Plugins
List all plugins:
GET /api/plugins/
List by category:
GET /api/plugins/by_category/
Get plugin details:
GET /api/plugins/{plugin_name}/
Execution Logs
List all logs:
GET /api/task-logs/
Filter by task:
GET /api/task-logs/?task_id=1
Filter by status:
GET /api/task-logs/?status=FAILED
Creating Custom Plugins
Step 1: Create Plugin Class
# myapp/plugins.py
from schedule.plugins import BasePlugin, register_plugin
@register_plugin
class MyCustomPlugin(BasePlugin):
name = "my_custom_plugin"
display_name = "My Custom Plugin"
description = "Does something awesome"
category = "automation"
config_schema = {
'api_key': {
'type': 'string',
'required': True,
'description': 'API key for external service',
},
'threshold': {
'type': 'integer',
'required': False,
'default': 100,
'description': 'Processing threshold',
},
'notification_template': {
'type': 'email_template',
'required': False,
'description': 'Email template to use for notifications',
},
}
def execute(self, context):
"""
context contains:
- business: Current tenant
- scheduled_task: ScheduledTask instance
- execution_time: When execution started
- user: User who created the task
"""
api_key = self.config.get('api_key')
threshold = self.config.get('threshold', 100)
# Do your task logic here
result = perform_some_operation(api_key, threshold)
return {
'success': True,
'message': 'Operation completed',
'data': {
'items_processed': result.count,
}
}
def can_execute(self, context):
"""Optional: Add pre-execution checks"""
if context['business'].is_suspended:
return False, "Business is suspended"
return True, None
def on_failure(self, error):
"""Optional: Handle failures"""
# Send alert, log to external service, etc.
pass
Step 2: Import Plugin on Startup
Add to your app's apps.py:
class MyAppConfig(AppConfig):
def ready(self):
from . import plugins # Import to register
Step 3: Use via API
curl -X POST http://lvh.me:8000/api/scheduled-tasks/ \
-H "Content-Type: application/json" \
-d '{
"name": "Custom Automation",
"plugin_name": "my_custom_plugin",
"plugin_config": {
"api_key": "secret123",
"threshold": 500
},
"schedule_type": "INTERVAL",
"interval_minutes": 60,
"status": "ACTIVE"
}'
Schedule Types
Cron Expression
{
"schedule_type": "CRON",
"cron_expression": "0 0 * * *" # Daily at midnight
}
# Examples:
# "*/5 * * * *" - Every 5 minutes
# "0 */2 * * *" - Every 2 hours
# "0 9 * * 1-5" - Weekdays at 9am
# "0 0 1 * *" - First day of month
Fixed Interval
{
"schedule_type": "INTERVAL",
"interval_minutes": 30 # Every 30 minutes
}
One-Time
{
"schedule_type": "ONE_TIME",
"run_at": "2025-12-01T10:00:00Z"
}
Execution Context
Every plugin receives a context dictionary:
{
'business': <Business instance>, # Current tenant
'scheduled_task': <ScheduledTask instance>,
'execution_time': <datetime>, # Execution timestamp
'user': <User instance or None>, # Task creator
}
Error Handling
Tasks automatically retry on failure with exponential backoff:
- First retry: 60 seconds
- Second retry: 120 seconds
- Third retry: 240 seconds
After 3 failures, the task is marked as failed and won't retry until next scheduled run.
Multi-Tenant Behavior
The scheduler respects django-tenants:
- Tasks execute in the context of their tenant's schema
- Each tenant has separate scheduled tasks
- Execution logs are tenant-isolated
- Plugins can access
context['business']for tenant info
Monitoring & Debugging
View Recent Logs
curl http://lvh.me:8000/api/task-logs/?limit=10
Check Failed Tasks
curl http://lvh.me:8000/api/task-logs/?status=FAILED
View Task Details
curl http://lvh.me:8000/api/scheduled-tasks/1/
Manual Test Execution
curl -X POST http://lvh.me:8000/api/scheduled-tasks/1/execute/
Best Practices
1. Plugin Design
- Keep plugins focused on one task
- Make config schema explicit
- Return structured results
- Handle errors gracefully
- Add logging for debugging
2. Configuration
- Use environment variables for secrets (not plugin_config)
- Validate config in
validate_config() - Provide sensible defaults
- Document config schema
3. Scheduling
- Use cron for predictable times (e.g., daily reports)
- Use intervals for continuous processing
- Avoid very short intervals (< 5 minutes)
- Consider business hours for customer-facing tasks
4. Error Handling
- Use
can_execute()for pre-flight checks - Return detailed error messages
- Implement
on_failure()for alerts - Don't silence exceptions in
execute()
5. Performance
- Keep executions fast (< 30 seconds ideal)
- Use pagination for large datasets
- Offload heavy work to dedicated queues
- Monitor execution times in logs
Testing
Test Plugin Directly
from schedule.plugins import registry
plugin = registry.get_instance('my_plugin', config={'key': 'value'})
result = plugin.execute({
'business': business,
'scheduled_task': None,
'execution_time': timezone.now(),
'user': None,
})
assert result['success'] == True
Test via API
import requests
# Create task
response = requests.post('http://lvh.me:8000/api/scheduled-tasks/', json={
'name': 'Test Task',
'plugin_name': 'send_email',
'plugin_config': {'recipients': ['test@example.com']},
'schedule_type': 'ONE_TIME',
'run_at': '2025-12-01T10:00:00Z',
})
task_id = response.json()['id']
# Execute immediately
requests.post(f'http://lvh.me:8000/api/scheduled-tasks/{task_id}/execute/')
# Check logs
logs = requests.get(f'http://lvh.me:8000/api/scheduled-tasks/{task_id}/logs/')
Troubleshooting
Plugins Not Registered
Check that apps.py imports the plugin module in ready().
Tasks Not Executing
- Check task status is ACTIVE
- Verify next_run_at is in the future
- Check Celery worker is running
- Look for errors in Django logs
Execution Logs Empty
Check that execute_scheduled_task Celery task is configured and workers are running.
Cron Expression Not Working
Use a cron validator. Common issues:
- Syntax errors
- Timezone mismatches (server runs in UTC)
Future Enhancements
Potential additions:
- Web UI for managing tasks
- Plugin marketplace
- Task dependencies/chaining
- Rate limiting
- Notifications on failure
- Dashboard with metrics
- Plugin versioning
- A/B testing for plugins