Back to Projects

Sapplify Tester Backend

Python Flask SQLAlchemy JWT Email Automation
2025
Sapplify Tester Admin Dashboard

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

I developed a comprehensive Flask-based API system that automates the entire beta testing workflow:

Skip to API Demo

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

API Demo: Explore the Endpoints

Explore the API structure and see simulated responses. This demo shows how the endpoints work without making real API calls.

GET /api/tests
-- -- ms

Response

// Click "Send Request" to see the simulated response

API Documentation

Authentication

Admin endpoints require JWT authentication. First, obtain a token by logging in with admin credentials, then include it in the Authorization header as "Bearer {token}".

Rate Limiting

API requests are rate-limited to prevent abuse. Current limits are 100 requests per minute for authenticated users.

Error Handling

All errors return a JSON object with an 'error' field containing the error message. HTTP status codes are used appropriately (400 for bad requests, 401 for unauthorized, etc.).

Other Projects