Overview
A comprehensive administrative platform built for managing app testing workflows at Sapplify. This backend API system handles the complete lifecycle of application testing - from managing test apps and accepting applications to implementing access code distribution and automated email communications. The platform streamlines the process of recruiting testers and distributing beta access codes for mobile applications.
Challenge
Sapplify needed an efficient system to manage their beta testing process, which involved coordinating between multiple stakeholders, handling hundreds of test applications, distributing limited access codes, and maintaining clear communication with selected testers. The existing manual process was time-consuming and prone to errors, leading to delays and missed opportunities.
Solution
System Architecture
Core Database Models
I designed a robust database schema with SQLAlchemy that handles complex relationships between tests, applications, users, and access codes. The schema ensures data integrity while providing flexibility for various testing scenarios.
# app/models/test.py
class TestApp(db.Model):
"""TestApp model for managing app testing campaigns"""
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text, nullable=False)
platforms = db.Column(db.String(50), nullable=False) # 'Android,iOS'
test_duration = db.Column(db.String(50), nullable=False)
spots_total = db.Column(db.Integer, nullable=False)
spots_taken = db.Column(db.Integer, default=0)
reward = db.Column(db.String(100), nullable=False)
is_active = db.Column(db.Boolean, default=True)
@property
def spots_available(self):
return max(0, self.spots_total - self.spots_taken)
def increment_spots_taken(self):
if self.spots_available <= 0:
return False
self.spots_taken += 1
return True
# app/models/application.py
class Application(db.Model):
"""Application model for managing tester applications"""
id = db.Column(db.String(36), primary_key=True)
test_id = db.Column(db.String(36), db.ForeignKey('test_app.id'), nullable=False)
name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(100), nullable=False)
device_type = db.Column(db.String(20), nullable=False)
device_model = db.Column(db.String(100), nullable=False)
status = db.Column(db.String(20), default=ApplicationStatus.PENDING)
feedback_reminder_sent = db.Column(db.Boolean, default=False)
RESTful API Design
The system exposes a comprehensive REST API with proper endpoint organization, authentication, and error handling. Each module focuses on specific functionality, making the API easy to maintain and extend.
# app/routes/applications.py
@applications.route('/api/applications', methods=['POST'])
def create_application():
"""
Public endpoint for submitting test applications
"""
data = request.json
# Validation
required_fields = ['test_id', 'name', 'email', 'device_type', 'device_model', 'os_version']
for field in required_fields:
if field not in data:
return jsonify({'error': f'Field {field} is required'}), 400
# Check test availability
test = TestApp.query.get(data['test_id'])
if not test or not test.is_active:
return jsonify({'error': 'Test not found or inactive'}), 404
if test.spots_available <= 0:
return jsonify({'error': 'No spots available'}), 400
# Create application and reserve spot
application = Application(
id=db.generate_uuid(),
test_id=data['test_id'],
name=data['name'],
email=data['email'],
device_type=data['device_type'],
device_model=data['device_model'],
os_version=data['os_version']
)
try:
test.increment_spots_taken()
db.session.add(application)
db.session.commit()
return jsonify({
'success': True,
'application_id': application.id
}), 201
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 500
@applications.route('/api/admin/applications/', methods=['PUT'])
@jwt_required()
def update_application(application_id):
"""
Admin endpoint for updating application status and sending emails
"""
application = Application.query.get(application_id)
if not application:
return jsonify({'error': 'Application not found'}), 404
data = request.json
old_status = application.status
if 'status' in data and data['status'] in ['approved', 'rejected']:
application.status = data['status']
# Automatic email notification
if old_status != data['status']:
try:
email_service.send_application_status_email(
application_id,
data['status']
)
except Exception as e:
logging.error(f"Email error: {str(e)}")
db.session.commit()
return jsonify({'success': True, 'application': application.to_dict()})
Advanced Email System
One of the most sophisticated components is the email automation system, which supports template-based emails, automatic status notifications, and reminder scheduling. The system is flexible and maintainable, allowing easy customization of email content.
# app/services/email_service.py
class EmailService:
"""Comprehensive email service with template support"""
def send_application_status_email(self, application_id, status):
"""Send status-specific emails with dynamic content"""
with self.app.app_context():
application = Application.query.get(application_id)
test = TestApp.query.get(application.test_id)
# Determine template based on status
if status == 'approved':
template = 'application_approved'
# For approved applications: assign access code
test_code = TestCode.query.filter_by(
test_id=test.id,
is_used=False,
assigned_to=None
).first()
if test_code:
test_code.is_used = True
test_code.assigned_to = application.id
test_code.assigned_at = datetime.utcnow()
db.session.commit()
code_value = test_code.code if test_code else None
else:
template = 'application_rejected'
code_value = None
# Context for template rendering
context = {
'application_id': application.id,
'name': application.name,
'email': application.email,
'test_name': test.name,
'test_description': test.description,
'access_code': code_value,
'platforms': test.platforms.split(',')
}
return self.send_template_email(
to=application.email,
template=template,
context=context
)
def send_reminder_emails(self):
"""Scheduled task for sending feedback reminders"""
cutoff_date = datetime.utcnow() - timedelta(days=14)
eligible_applications = Application.query.filter(
Application.status == 'approved',
Application.updated_at <= cutoff_date,
Application.feedback_reminder_sent == False
).all()
sent_count = 0
for app in eligible_applications:
context = {
'name': app.name,
'test_name': app.test.name,
'feedback_form_link': self.feedback_form_link
}
success, error = self.send_template_email(
to=app.email,
template='feedback_reminder',
context=context
)
if success:
app.feedback_reminder_sent = True
app.feedback_reminder_date = datetime.utcnow()
db.session.commit()
sent_count += 1
return sent_count
Access Code Management
The system includes a sophisticated access code management system that handles code generation, distribution, and tracking. Codes are automatically assigned to approved applications and can be managed through bulk operations.
# app/routes/test_codes.py
@test_codes.route('/api/admin/tests//codes/generate', methods=['POST'])
@jwt_required()
def generate_test_codes(test_id):
"""Generate random access codes for a test"""
import random
import string
test = TestApp.query.get(test_id)
if not test:
return jsonify({'error': 'Test not found'}), 404
data = request.json
count = int(data['count'])
if count <= 0 or count > 1000:
return jsonify({'error': 'Count must be between 1 and 1000'}), 400
code_length = data.get('length', 16)
use_dashes = data.get('use_dashes', True)
generated_codes = []
for _ in range(count):
if use_dashes:
# Format: XXXX-XXXX-XXXX
segments = []
segment_length = code_length // 4
for i in range(4):
segment = ''.join(random.choices(
string.ascii_uppercase + string.digits,
k=segment_length
))
segments.append(segment)
code = '-'.join(segments)
else:
code = ''.join(random.choices(
string.ascii_uppercase + string.digits,
k=code_length
))
# Ensure uniqueness
if not TestCode.query.filter_by(code=code).first():
test_code = TestCode(
id=db.generate_uuid(),
test_id=test_id,
code=code,
is_used=False
)
db.session.add(test_code)
generated_codes.append(code)
db.session.commit()
return jsonify({
'success': True,
'codes_added': len(generated_codes),
'generated_codes': generated_codes[:20] # Show first 20
})
Security and Authentication
The platform implements JWT-based authentication with proper security measures. Admin routes are protected, and the system includes features like password hashing and session management.
# app/routes/auth.py
@auth.route('/api/admin/auth/login', methods=['POST'])
def admin_login():
"""Admin authentication endpoint"""
username = request.json.get('username')
password = request.json.get('password')
admin = Admin.query.filter_by(username=username).first()
if not admin or not admin.check_password(password):
return jsonify({'error': 'Invalid credentials'}), 401
access_token = create_access_token(identity=str(admin.id))
return jsonify({
'access_token': access_token,
'username': admin.username
})
@auth.route('/api/admin/auth/check', methods=['GET'])
@jwt_required()
def check_auth():
"""Validate JWT token and return admin info"""
admin_id = get_jwt_identity()
admin = Admin.query.get(admin_id)
if not admin:
return jsonify({'error': 'Admin not found'}), 404
return jsonify({
'id': admin.id,
'username': admin.username
})
# app/models/admin.py
class Admin(db.Model):
"""Admin user model with secure password handling"""
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
Key Technical Features
- Modular Architecture: Clean separation of concerns with models, services, and routes
- Database Relationships: Complex many-to-many relationships with proper foreign key constraints
- Template System: Dynamic email templates with Jinja2 rendering
- Bulk Operations: Efficient batch processing for codes and emails
- Error Handling: Comprehensive error handling with meaningful messages
- Logging System: Configurable logging with multiple handlers
- Configuration Management: Environment-specific configurations
- API Documentation: Clear endpoint documentation with proper HTTP status codes
Implementation Highlights
Dynamic Email Templates
Created a flexible email template system that supports HTML and plain text versions with dynamic content rendering using Jinja2.
Automated Code Distribution
Implemented automatic access code assignment when applications are approved, ensuring each tester receives a unique code.
Scalable Database Design
Designed the database schema to handle multiple concurrent test campaigns with proper indexing and relationship management.
Background Task Processing
Implemented scheduled tasks for sending reminder emails and maintaining system health checks.
API Endpoints Overview
Public Endpoints
POST /api/applications
- Submit test application
GET /api/applications/{id}/status
- Check application status
GET /api/tests
- List active tests
POST /api/feedback/{id}
- Submit feedback
Admin Endpoints
POST /api/admin/auth/login
- Admin authentication
GET /api/admin/applications
- Manage applications
PUT /api/admin/applications/{id}
- Update application status
POST /api/admin/tests
- Create new test
POST /api/admin/tests/{id}/codes/generate
- Generate access codes
GET /api/admin/email-templates
- Manage email templates