|
| 1 | +""" |
| 2 | +Email service for sending quiz invitations via Resend. |
| 3 | +""" |
| 4 | +import resend |
| 5 | +from django.conf import settings |
| 6 | +from django.utils import timezone |
| 7 | + |
| 8 | + |
| 9 | +def send_quiz_invitations(quiz, invitations, request=None): |
| 10 | + """ |
| 11 | + Send quiz invitation emails to students. |
| 12 | +
|
| 13 | + Args: |
| 14 | + quiz: QuizSession instance |
| 15 | + invitations: List of QuizInvitation instances |
| 16 | + request: Optional HTTP request for building URLs |
| 17 | +
|
| 18 | + Returns: |
| 19 | + dict with sent, failed counts and errors list |
| 20 | + """ |
| 21 | + resend.api_key = settings.RESEND_API_KEY |
| 22 | + |
| 23 | + # Build base URL |
| 24 | + if request: |
| 25 | + base_url = f"{request.scheme}://{request.get_host()}" |
| 26 | + else: |
| 27 | + base_url = getattr(settings, 'BASE_URL', 'https://mkt-production.up.railway.app') |
| 28 | + |
| 29 | + from_email = getattr(settings, 'FROM_EMAIL', 'quizzes@updates.yourdomain.com') |
| 30 | + |
| 31 | + sent = 0 |
| 32 | + failed = 0 |
| 33 | + errors = [] |
| 34 | + |
| 35 | + for invitation in invitations: |
| 36 | + quiz_url = f"{base_url}/quiz/{invitation.code}/" |
| 37 | + |
| 38 | + try: |
| 39 | + # Build email content |
| 40 | + subject = f"Quiz Invitation: {quiz.name}" |
| 41 | + |
| 42 | + html_content = f""" |
| 43 | + <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;"> |
| 44 | + <h2 style="color: #0284c7;">Quiz Invitation</h2> |
| 45 | +
|
| 46 | + <p>Hello {invitation.student_name or 'Student'},</p> |
| 47 | +
|
| 48 | + <p>You have been invited to take the following quiz:</p> |
| 49 | +
|
| 50 | + <div style="background: #f0f9ff; padding: 20px; border-radius: 8px; margin: 20px 0;"> |
| 51 | + <h3 style="margin: 0 0 10px 0; color: #0369a1;">{quiz.name}</h3> |
| 52 | + {f'<p style="margin: 0; color: #64748b;">{quiz.description}</p>' if quiz.description else ''} |
| 53 | + <p style="margin: 10px 0 0 0; color: #64748b;"> |
| 54 | + Time limit: {quiz.time_limit_minutes} minutes |
| 55 | + </p> |
| 56 | + </div> |
| 57 | +
|
| 58 | + <p>Click the button below to start your quiz:</p> |
| 59 | +
|
| 60 | + <a href="{quiz_url}" |
| 61 | + style="display: inline-block; background: #0284c7; color: white; |
| 62 | + padding: 12px 24px; text-decoration: none; border-radius: 6px; |
| 63 | + font-weight: bold; margin: 10px 0;"> |
| 64 | + Start Quiz |
| 65 | + </a> |
| 66 | +
|
| 67 | + <p style="color: #64748b; font-size: 14px; margin-top: 20px;"> |
| 68 | + Or copy this link: <a href="{quiz_url}">{quiz_url}</a> |
| 69 | + </p> |
| 70 | +
|
| 71 | + <p style="color: #94a3b8; font-size: 12px; margin-top: 30px;"> |
| 72 | + This is your personal quiz link. Do not share it with others. |
| 73 | + </p> |
| 74 | + </div> |
| 75 | + """ |
| 76 | + |
| 77 | + text_content = f""" |
| 78 | +Quiz Invitation: {quiz.name} |
| 79 | +
|
| 80 | +Hello {invitation.student_name or 'Student'}, |
| 81 | +
|
| 82 | +You have been invited to take the quiz: {quiz.name} |
| 83 | +
|
| 84 | +Time limit: {quiz.time_limit_minutes} minutes |
| 85 | +
|
| 86 | +Click here to start: {quiz_url} |
| 87 | +
|
| 88 | +This is your personal quiz link. Do not share it with others. |
| 89 | + """ |
| 90 | + |
| 91 | + # Send via Resend |
| 92 | + response = resend.Emails.send({ |
| 93 | + "from": from_email, |
| 94 | + "to": [invitation.student_email], |
| 95 | + "subject": subject, |
| 96 | + "html": html_content, |
| 97 | + "text": text_content |
| 98 | + }) |
| 99 | + |
| 100 | + # Update invitation |
| 101 | + invitation.email_sent_at = timezone.now() |
| 102 | + invitation.email_error = '' |
| 103 | + invitation.save(update_fields=['email_sent_at', 'email_error']) |
| 104 | + sent += 1 |
| 105 | + |
| 106 | + except Exception as e: |
| 107 | + invitation.email_error = str(e) |
| 108 | + invitation.save(update_fields=['email_error']) |
| 109 | + errors.append(f"{invitation.student_email}: {str(e)}") |
| 110 | + failed += 1 |
| 111 | + |
| 112 | + return { |
| 113 | + 'sent': sent, |
| 114 | + 'failed': failed, |
| 115 | + 'errors': errors |
| 116 | + } |
0 commit comments