agdar/CONSENT_EMAIL_SIGNING_IMPLEMENTATION.md
2025-11-02 14:35:35 +03:00

30 KiB

Consent Email Signing Implementation Plan

Date: October 30, 2025
Feature: Email-based Consent Signing for Parents/Guardians
Requirement: Parents/guardians can sign consent forms via email without logging in


Overview

Implement a secure, email-based consent signing workflow that allows parents/guardians to:

  1. Receive consent forms via email
  2. Review consent forms online
  3. Sign consent forms electronically
  4. Complete the process without creating an account or logging in

Architecture

Flow Diagram

Staff creates consent → System generates secure token → Email sent to parent
                                                              ↓
                                                    Parent clicks link
                                                              ↓
                                                    Public consent page
                                                              ↓
                                                    Parent reviews & signs
                                                              ↓
                                                    Consent recorded in system
                                                              ↓
                                                    Confirmation email sent

Implementation Steps

1. Database Changes

Add ConsentToken Model (core/models.py)

class ConsentToken(UUIDPrimaryKeyMixin, TimeStampedMixin):
    """
    Secure token for email-based consent signing.
    
    Allows parents/guardians to sign consent without logging in.
    """
    
    consent = models.ForeignKey(
        'Consent',
        on_delete=models.CASCADE,
        related_name='tokens',
        verbose_name=_("Consent")
    )
    token = models.CharField(
        max_length=64,
        unique=True,
        verbose_name=_("Token"),
        help_text=_("Secure token for accessing consent form")
    )
    email = models.EmailField(
        verbose_name=_("Email Address"),
        help_text=_("Email address where consent link was sent")
    )
    expires_at = models.DateTimeField(
        verbose_name=_("Expires At"),
        help_text=_("Token expiration date/time")
    )
    used_at = models.DateTimeField(
        null=True,
        blank=True,
        verbose_name=_("Used At"),
        help_text=_("When the token was used to sign consent")
    )
    is_active = models.BooleanField(
        default=True,
        verbose_name=_("Is Active")
    )
    sent_by = models.ForeignKey(
        'User',
        on_delete=models.SET_NULL,
        null=True,
        related_name='sent_consent_tokens',
        verbose_name=_("Sent By")
    )
    
    class Meta:
        verbose_name = _("Consent Token")
        verbose_name_plural = _("Consent Tokens")
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['token']),
            models.Index(fields=['email']),
            models.Index(fields=['expires_at']),
        ]
    
    def __str__(self):
        return f"Token for {self.consent} - {self.email}"
    
    def is_valid(self):
        """Check if token is still valid."""
        from django.utils import timezone
        return (
            self.is_active and
            not self.used_at and
            self.expires_at > timezone.now()
        )
    
    def mark_as_used(self):
        """Mark token as used."""
        from django.utils import timezone
        self.used_at = timezone.now()
        self.is_active = False
        self.save()
    
    @staticmethod
    def generate_token():
        """Generate a secure random token."""
        import secrets
        return secrets.token_urlsafe(48)

Migration Command

python manage.py makemigrations core
python manage.py migrate

2. Service Layer

Add ConsentEmailService (core/services.py)

class ConsentEmailService:
    """Service for email-based consent signing."""
    
    @staticmethod
    @transaction.atomic
    def send_consent_for_signing(
        consent: Consent,
        email: str,
        sent_by: User,
        expiry_hours: int = 72
    ) -> ConsentToken:
        """
        Send consent form to parent/guardian for signing.
        
        Args:
            consent: Consent instance to send
            email: Email address to send to
            sent_by: User sending the consent
            expiry_hours: Hours until token expires (default 72)
            
        Returns:
            ConsentToken: Created token instance
        """
        from django.utils import timezone
        from datetime import timedelta
        
        # Generate secure token
        token_string = ConsentToken.generate_token()
        
        # Calculate expiry
        expires_at = timezone.now() + timedelta(hours=expiry_hours)
        
        # Create token
        token = ConsentToken.objects.create(
            consent=consent,
            token=token_string,
            email=email,
            expires_at=expires_at,
            sent_by=sent_by
        )
        
        # Send email
        ConsentEmailService._send_consent_email(token)
        
        logger.info(
            f"Consent signing link sent to {email} for consent {consent.id}"
        )
        
        return token
    
    @staticmethod
    def _send_consent_email(token: ConsentToken):
        """Send email with consent signing link."""
        from django.core.mail import send_mail
        from django.template.loader import render_to_string
        from django.conf import settings
        from django.urls import reverse
        
        # Build signing URL
        signing_url = settings.SITE_URL + reverse(
            'core:consent_sign_public',
            kwargs={'token': token.token}
        )
        
        # Prepare email context
        context = {
            'consent': token.consent,
            'patient': token.consent.patient,
            'signing_url': signing_url,
            'expires_at': token.expires_at,
            'clinic_name': token.consent.tenant.name,
        }
        
        # Render email templates
        subject = f"Consent Form for {token.consent.patient.full_name_en}"
        html_message = render_to_string(
            'emails/consent_signing_request.html',
            context
        )
        text_message = render_to_string(
            'emails/consent_signing_request.txt',
            context
        )
        
        # Send email
        send_mail(
            subject=subject,
            message=text_message,
            from_email=settings.DEFAULT_FROM_EMAIL,
            recipient_list=[token.email],
            html_message=html_message,
            fail_silently=False,
        )
    
    @staticmethod
    def verify_token(token_string: str) -> Tuple[bool, str, Optional[ConsentToken]]:
        """
        Verify if token is valid.
        
        Args:
            token_string: Token string to verify
            
        Returns:
            Tuple[bool, str, Optional[ConsentToken]]: (is_valid, message, token)
        """
        try:
            token = ConsentToken.objects.select_related(
                'consent', 'consent__patient'
            ).get(token=token_string)
            
            if not token.is_valid():
                if token.used_at:
                    return False, "This consent has already been signed.", None
                elif token.expires_at < timezone.now():
                    return False, "This link has expired. Please request a new one.", None
                else:
                    return False, "This link is no longer valid.", None
            
            return True, "Token is valid.", token
            
        except ConsentToken.DoesNotExist:
            return False, "Invalid consent link.", None
    
    @staticmethod
    @transaction.atomic
    def sign_consent_via_token(
        token: ConsentToken,
        signed_by_name: str,
        signed_by_relationship: str,
        signature_method: str,
        signature_image=None,
        signed_ip: str = None,
        signed_user_agent: str = None
    ) -> Consent:
        """
        Sign consent using email token.
        
        Args:
            token: ConsentToken instance
            signed_by_name: Name of person signing
            signed_by_relationship: Relationship to patient
            signature_method: Method used for signature
            signature_image: Optional signature image
            signed_ip: IP address of signer
            signed_user_agent: User agent of signer
            
        Returns:
            Consent: Signed consent instance
        """
        # Sign the consent
        consent = ConsentService.sign_consent(
            consent=token.consent,
            signed_by_name=signed_by_name,
            signed_by_relationship=signed_by_relationship,
            signature_method=signature_method,
            signature_image=signature_image,
            signed_ip=signed_ip,
            signed_user_agent=signed_user_agent
        )
        
        # Mark token as used
        token.mark_as_used()
        
        # Send confirmation email
        ConsentEmailService._send_confirmation_email(token, consent)
        
        logger.info(
            f"Consent {consent.id} signed via email token by {signed_by_name}"
        )
        
        return consent
    
    @staticmethod
    def _send_confirmation_email(token: ConsentToken, consent: Consent):
        """Send confirmation email after consent is signed."""
        from django.core.mail import send_mail
        from django.template.loader import render_to_string
        from django.conf import settings
        
        context = {
            'consent': consent,
            'patient': consent.patient,
            'signed_by_name': consent.signed_by_name,
            'signed_at': consent.signed_at,
            'clinic_name': consent.tenant.name,
        }
        
        subject = f"Consent Form Signed - {consent.patient.full_name_en}"
        html_message = render_to_string(
            'emails/consent_signed_confirmation.html',
            context
        )
        text_message = render_to_string(
            'emails/consent_signed_confirmation.txt',
            context
        )
        
        send_mail(
            subject=subject,
            message=text_message,
            from_email=settings.DEFAULT_FROM_EMAIL,
            recipient_list=[token.email],
            html_message=html_message,
            fail_silently=False,
        )

3. Views

class ConsentSignPublicView(TemplateView):
    """
    Public view for signing consent via email link.
    
    No authentication required.
    """
    template_name = 'core/consent_sign_public.html'
    
    def get_context_data(self, **kwargs):
        """Add token and consent to context."""
        context = super().get_context_data(**kwargs)
        
        token_string = self.kwargs.get('token')
        
        # Verify token
        is_valid, message, token = ConsentEmailService.verify_token(token_string)
        
        context['is_valid'] = is_valid
        context['message'] = message
        context['token'] = token
        
        if token:
            context['consent'] = token.consent
            context['patient'] = token.consent.patient
        
        return context


class ConsentSignPublicSubmitView(View):
    """
    Handle consent signing submission from public form.
    
    No authentication required.
    """
    
    def post(self, request, token):
        """Process consent signing."""
        # Verify token
        is_valid, message, token_obj = ConsentEmailService.verify_token(token)
        
        if not is_valid:
            messages.error(request, message)
            return redirect('core:consent_sign_public', token=token)
        
        # Get form data
        signed_by_name = request.POST.get('signed_by_name')
        signed_by_relationship = request.POST.get('signed_by_relationship')
        signature_method = request.POST.get('signature_method', 'TYPED')
        signature_data = request.POST.get('signature_data')  # Base64 if drawn
        
        # Validate required fields
        if not signed_by_name or not signed_by_relationship:
            messages.error(request, "Please provide your name and relationship.")
            return redirect('core:consent_sign_public', token=token)
        
        # Handle signature image if drawn
        signature_image = None
        if signature_method == 'DRAWN' and signature_data:
            # Convert base64 to image file
            import base64
            from django.core.files.base import ContentFile
            
            format, imgstr = signature_data.split(';base64,')
            ext = format.split('/')[-1]
            signature_image = ContentFile(
                base64.b64decode(imgstr),
                name=f'signature_{token_obj.consent.id}.{ext}'
            )
        
        # Get IP and user agent
        signed_ip = self.get_client_ip(request)
        signed_user_agent = request.META.get('HTTP_USER_AGENT', '')[:255]
        
        try:
            # Sign consent
            consent = ConsentEmailService.sign_consent_via_token(
                token=token_obj,
                signed_by_name=signed_by_name,
                signed_by_relationship=signed_by_relationship,
                signature_method=signature_method,
                signature_image=signature_image,
                signed_ip=signed_ip,
                signed_user_agent=signed_user_agent
            )
            
            # Success
            return render(request, 'core/consent_sign_success.html', {
                'consent': consent,
                'patient': consent.patient,
            })
            
        except Exception as e:
            logger.error(f"Error signing consent via token: {e}")
            messages.error(request, "An error occurred while signing the consent. Please try again.")
            return redirect('core:consent_sign_public', token=token)
    
    def get_client_ip(self, request):
        """Get client IP address."""
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            ip = x_forwarded_for.split(',')[0]
        else:
            ip = request.META.get('REMOTE_ADDR')
        return ip


class ConsentSendEmailView(LoginRequiredMixin, RolePermissionMixin, View):
    """
    Staff view to send consent form via email.
    
    Requires authentication.
    """
    allowed_roles = [User.Role.ADMIN, User.Role.DOCTOR, User.Role.NURSE, 
                    User.Role.FRONT_DESK]
    
    def post(self, request, consent_id):
        """Send consent form via email."""
        try:
            consent = Consent.objects.get(
                id=consent_id,
                tenant=request.user.tenant
            )
            
            # Get email from form
            email = request.POST.get('email')
            if not email:
                messages.error(request, "Please provide an email address.")
                return redirect('core:consent_detail', pk=consent_id)
            
            # Send consent
            token = ConsentEmailService.send_consent_for_signing(
                consent=consent,
                email=email,
                sent_by=request.user,
                expiry_hours=72  # 3 days
            )
            
            messages.success(
                request,
                f"Consent form sent to {email}. Link expires in 72 hours."
            )
            
            return redirect('core:consent_detail', pk=consent_id)
            
        except Consent.DoesNotExist:
            messages.error(request, "Consent not found.")
            return redirect('core:consent_list')
        except Exception as e:
            logger.error(f"Error sending consent email: {e}")
            messages.error(request, "Failed to send email. Please try again.")
            return redirect('core:consent_detail', pk=consent_id)

4. URLs

Add URL Patterns (core/urls.py)

from django.urls import path
from .views import (
    # ... existing views ...
    ConsentSignPublicView,
    ConsentSignPublicSubmitView,
    ConsentSendEmailView,
)

app_name = 'core'

urlpatterns = [
    # ... existing patterns ...
    
    # Public consent signing (no auth required)
    path(
        'consent/sign/<str:token>/',
        ConsentSignPublicView.as_view(),
        name='consent_sign_public'
    ),
    path(
        'consent/sign/<str:token>/submit/',
        ConsentSignPublicSubmitView.as_view(),
        name='consent_sign_public_submit'
    ),
    
    # Staff: Send consent via email
    path(
        'consent/<uuid:consent_id>/send-email/',
        ConsentSendEmailView.as_view(),
        name='consent_send_email'
    ),
]

5. Email Templates

Request Email (templates/emails/consent_signing_request.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Consent Form - {{ clinic_name }}</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
    <div style="max-width: 600px; margin: 0 auto; padding: 20px;">
        <h2 style="color: #2c3e50;">Consent Form for {{ patient.full_name_en }}</h2>
        
        <p>Dear Parent/Guardian,</p>
        
        <p>{{ clinic_name }} requires your consent for treatment services for <strong>{{ patient.full_name_en }}</strong>.</p>
        
        <p>Please review and sign the consent form by clicking the button below:</p>
        
        <div style="text-align: center; margin: 30px 0;">
            <a href="{{ signing_url }}" 
               style="background-color: #3498db; color: white; padding: 12px 30px; 
                      text-decoration: none; border-radius: 5px; display: inline-block;">
                Review and Sign Consent
            </a>
        </div>
        
        <p><strong>Important:</strong></p>
        <ul>
            <li>This link will expire on {{ expires_at|date:"F d, Y at g:i A" }}</li>
            <li>You do not need to create an account or log in</li>
            <li>The consent form can only be signed once</li>
        </ul>
        
        <p>If you have any questions, please contact us at {{ clinic_name }}.</p>
        
        <hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
        
        <p style="font-size: 12px; color: #7f8c8d;">
            This is an automated message from {{ clinic_name }}. 
            If you received this email in error, please disregard it.
        </p>
    </div>
</body>
</html>

Request Email Text Version (templates/emails/consent_signing_request.txt)

Consent Form for {{ patient.full_name_en }}

Dear Parent/Guardian,

{{ clinic_name }} requires your consent for treatment services for {{ patient.full_name_en }}.

Please review and sign the consent form by visiting this link:
{{ signing_url }}

IMPORTANT:
- This link will expire on {{ expires_at|date:"F d, Y at g:i A" }}
- You do not need to create an account or log in
- The consent form can only be signed once

If you have any questions, please contact us at {{ clinic_name }}.

---
This is an automated message from {{ clinic_name }}.
If you received this email in error, please disregard it.

Confirmation Email (templates/emails/consent_signed_confirmation.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Consent Signed - {{ clinic_name }}</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
    <div style="max-width: 600px; margin: 0 auto; padding: 20px;">
        <h2 style="color: #27ae60;">✓ Consent Form Signed Successfully</h2>
        
        <p>Dear {{ signed_by_name }},</p>
        
        <p>Thank you for signing the consent form for <strong>{{ patient.full_name_en }}</strong>.</p>
        
        <div style="background-color: #f8f9fa; padding: 15px; border-left: 4px solid #27ae60; margin: 20px 0;">
            <p style="margin: 0;"><strong>Consent Details:</strong></p>
            <ul style="margin: 10px 0;">
                <li>Patient: {{ patient.full_name_en }}</li>
                <li>Signed by: {{ signed_by_name }}</li>
                <li>Signed on: {{ signed_at|date:"F d, Y at g:i A" }}</li>
                <li>Consent Type: {{ consent.get_consent_type_display }}</li>
            </ul>
        </div>
        
        <p>A copy of this consent has been recorded in our system.</p>
        
        <p>If you have any questions, please contact {{ clinic_name }}.</p>
        
        <hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
        
        <p style="font-size: 12px; color: #7f8c8d;">
            This is an automated confirmation from {{ clinic_name }}.
        </p>
    </div>
</body>
</html>

Main Template (templates/core/consent_sign_public.html)

{% extends "base_public.html" %}
{% load static %}

{% block title %}Sign Consent Form{% endblock %}

{% block content %}
<div class="container mt-5">
    <div class="row justify-content-center">
        <div class="col-md-8">
            {% if not is_valid %}
                <div class="alert alert-danger">
                    <h4>Invalid or Expired Link</h4>
                    <p>{{ message }}</p>
                    <p>Please contact the clinic for a new consent link.</p>
                </div>
            {% else %}
                <div class="card">
                    <div class="card-header bg-primary text-white">
                        <h3>Consent Form</h3>
                    </div>
                    <div class="card-body">
                        <h4>Patient: {{ patient.full_name_en }}</h4>
                        <p class="text-muted">MRN: {{ patient.mrn }}</p>
                        
                        <hr>
                        
                        <h5>{{ consent.get_consent_type_display }}</h5>
                        
                        <div class="consent-content" style="max-height: 400px; overflow-y: auto; 
                                                            border: 1px solid #ddd; padding: 15px; 
                                                            margin: 20px 0; background-color: #f8f9fa;">
                            {{ consent.content_text|linebreaks }}
                        </div>
                        
                        <form method="post" action="{% url 'core:consent_sign_public_submit' token=token.token %}" 
                              id="consentForm">
                            {% csrf_token %}
                            
                            <div class="form-group">
                                <label for="signed_by_name">Your Full Name *</label>
                                <input type="text" class="form-control" id="signed_by_name" 
                                       name="signed_by_name" required>
                            </div>
                            
                            <div class="form-group">
                                <label for="signed_by_relationship">Relationship to Patient *</label>
                                <select class="form-control" id="signed_by_relationship" 
                                        name="signed_by_relationship" required>
                                    <option value="">Select...</option>
                                    <option value="Mother">Mother</option>
                                    <option value="Father">Father</option>
                                    <option value="Guardian">Legal Guardian</option>
                                    <option value="Other">Other</option>
                                </select>
                            </div>
                            
                            <div class="form-group">
                                <label>Signature Method *</label>
                                <div class="btn-group btn-group-toggle d-block" data-toggle="buttons">
                                    <label class="btn btn-outline-primary active">
                                        <input type="radio" name="signature_method" value="TYPED" checked> 
                                        Type Name
                                    </label>
                                    <label class="btn btn-outline-primary">
                                        <input type="radio" name="signature_method" value="DRAWN"> 
                                        Draw Signature
                                    </label>
                                </div>
                            </div>
                            
                            <div id="signaturePad" style="display: none;">
                                <label>Draw Your Signature</label>
                                <canvas id="signatureCanvas" 
                                        style="border: 1px solid #000; width: 100%; height: 200px;">
                                </canvas>
                                <button type="button" class="btn btn-sm btn-secondary mt-2" 
                                        onclick="clearSignature()">
                                    Clear
                                </button>
                                <input type="hidden" name="signature_data" id="signatureData">
                            </div>
                            
                            <div class="form-check mt-3">
                                <input type="checkbox" class="form-check-input" id="agreeCheck" required>
                                <label class="form-check-label" for="agreeCheck">
                                    I have read and agree to the above consent form *
                                </label>
                            </div>
                            
                            <button type="submit" class="btn btn-primary btn-lg btn-block mt-4">
                                Sign Consent Form
                            </button>
                        </form>
                    </div>
                </div>
                
                <p class="text-center text-muted mt-3">
                    <small>This link expires on {{ token.expires_at|date:"F d, Y at g:i A" }}</small>
                </p>
            {% endif %}
        </div>
    </div>
</div>

<script>
// Signature pad functionality
let canvas, ctx, isDrawing = false;

document.querySelectorAll('input[name="signature_method"]').forEach(radio => {
    radio.addEventListener('change', function() {
        const signaturePad = document.getElementById('signaturePad');
        if (this.value === 'DRAWN') {
            signaturePad.style.display = 'block';
            initSignaturePad();
        } else {
            signaturePad.style.display = 'none';
        }
    });
});

function initSignaturePad() {
    canvas = document.getElementById('signatureCanvas');
    ctx = canvas.getContext('2d');
    
    // Set canvas size
    canvas.width = canvas.offsetWidth;
    canvas.height = 200;
    
    // Mouse events
    canvas.addEventListener('mousedown', startDrawing);
    canvas.addEventListener('mousemove', draw);
    canvas.addEventListener('mouseup', stopDrawing);
    canvas.addEventListener('mouseout', stopDrawing);
    
    // Touch events
    canvas.addEventListener('touchstart', handleTouch);
    canvas.addEventListener('touchmove', handleTouch);
    canvas.addEventListener('touchend', stopDrawing);
}

function startDrawing(e) {
    isDrawing = true;
    ctx.beginPath();
    ctx.moveTo(e.offsetX, e.offsetY);
}

function draw(e) {
    if (!isDrawing) return;
    ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke();
}

function stopDrawing() {
    if (isDrawing) {
        isDrawing = false;
        // Save signature as base64
        document.getElementById('signatureData').value = canvas.toDataURL();
    }
}

function handleTouch(e) {
    e.preventDefault();
    const touch = e.touches[0];
    const rect = canvas.getBoundingClientRect();
    const x = touch.clientX - rect.left;
    const y = touch.clientY - rect.top;
    
    if (e.type === 'touchstart') {
        isDrawing = true;
        ctx.beginPath();
        ctx.moveTo(x, y);
    } else if (e.type === 'touchmove' && isDrawing) {
        ctx.lineTo(x, y);
        ctx.stroke();
    }
}

function clearSignature() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    document.getElementById('signatureData').value = '';
}
</script>
{% endblock %}

7. Settings Configuration

Add to AgdarCentre/settings.py

# Site URL for email links
SITE_URL = env('SITE_URL', default='http://localhost:8000')

# Email configuration
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = env('EMAIL_HOST', default='smtp.gmail.com')
EMAIL_PORT = env.int('EMAIL_PORT', default=587)
EMAIL_USE_TLS = env.bool('EMAIL_USE_TLS', default=True)
EMAIL_HOST_USER = env('EMAIL_HOST_USER', default='')
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD', default='')
DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL', default='noreply@agdarcentre.com')

Add to .env

SITE_URL=https://yourdomain.com
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=your-email@gmail.com
EMAIL_HOST_PASSWORD=your-app-password
DEFAULT_FROM_EMAIL=noreply@agdarcentre.com

8. Admin Interface

Add to core/admin.py

@admin.register(ConsentToken)
class ConsentTokenAdmin(admin.ModelAdmin):
    """Admin interface for Consent Tokens."""
    list_display = ['consent', 'email', 'created_at', 'expires_at', 'used_at', 'is_active']
    list_filter = ['is_active', 'created_at', 'expires_at']
    search_fields = ['email', 'consent__patient__first_name_en', 'consent__patient__mrn']
    readonly_fields = ['token', 'created_at', 'used_at']
    date_hierarchy = 'created_at'

9. Staff UI Integration

<!-- In consent detail template -->
<div class="card-footer">
    <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#sendEmailModal">
        <i class="fas fa-envelope"></i> Send via Email
    </button>
</div>

<!-- Email Modal -->
<div class="modal fade" id="sendEmailModal" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <form method="