Skip to content

Commit f2584bb

Browse files
committed
Add retention warning email template
1 parent 5de0b9a commit f2584bb

File tree

4 files changed

+431
-34
lines changed

4 files changed

+431
-34
lines changed

apps/challenges/aws_utils.py

Lines changed: 121 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
from django.conf import settings
1515
from django.core import serializers
1616
from django.core.files.temp import NamedTemporaryFile
17+
from django.core.mail import EmailMultiAlternatives
18+
from django.template.loader import render_to_string
19+
from django.utils.html import strip_tags
1720

1821
from evalai.celery import app
1922

@@ -2309,6 +2312,103 @@ def update_submission_retention_dates():
23092312
return {"updated_submissions": updated_count, "errors": errors}
23102313

23112314

2315+
def send_template_email(
2316+
recipient_email,
2317+
subject,
2318+
template_name,
2319+
template_context,
2320+
sender_email=None,
2321+
reply_to=None,
2322+
):
2323+
"""
2324+
Send an email using Django templates instead of SendGrid.
2325+
2326+
Args:
2327+
recipient_email (str): Email address of the recipient
2328+
subject (str): Email subject line
2329+
template_name (str): Template name (e.g., 'challenges/retention_warning.html')
2330+
template_context (dict): Context data for the template
2331+
sender_email (str, optional): Sender email address. Defaults to CLOUDCV_TEAM_EMAIL
2332+
reply_to (str, optional): Reply-to email address
2333+
2334+
Returns:
2335+
bool: True if email was sent successfully, False otherwise
2336+
"""
2337+
try:
2338+
# Use default sender if not provided
2339+
if not sender_email:
2340+
sender_email = settings.CLOUDCV_TEAM_EMAIL
2341+
2342+
# Render the HTML template
2343+
html_content = render_to_string(template_name, template_context)
2344+
2345+
# Create plain text version by stripping HTML tags
2346+
text_content = strip_tags(html_content)
2347+
2348+
# Create email message
2349+
email = EmailMultiAlternatives(
2350+
subject=subject,
2351+
body=text_content,
2352+
from_email=sender_email,
2353+
to=[recipient_email],
2354+
reply_to=[reply_to] if reply_to else None,
2355+
)
2356+
2357+
# Attach HTML version
2358+
email.attach_alternative(html_content, "text/html")
2359+
2360+
# Send the email
2361+
email.send()
2362+
2363+
logger.info(f"Email sent successfully to {recipient_email}")
2364+
return True
2365+
2366+
except Exception as e:
2367+
logger.error(f"Failed to send email to {recipient_email}: {str(e)}")
2368+
return False
2369+
2370+
2371+
def send_retention_warning_email(
2372+
challenge, recipient_email, submission_count, warning_date
2373+
):
2374+
"""
2375+
Send retention warning email using Django template.
2376+
2377+
Args:
2378+
challenge: Challenge object
2379+
recipient_email (str): Email address of the recipient
2380+
submission_count (int): Number of submissions affected
2381+
warning_date (datetime): Date when cleanup will occur
2382+
2383+
Returns:
2384+
bool: True if email was sent successfully, False otherwise
2385+
"""
2386+
# Prepare template context
2387+
template_context = {
2388+
"CHALLENGE_NAME": challenge.title,
2389+
"CHALLENGE_URL": f"{settings.EVALAI_API_SERVER}/web/challenges/challenge-page/{challenge.id}",
2390+
"SUBMISSION_COUNT": submission_count,
2391+
"RETENTION_DATE": warning_date.strftime("%B %d, %Y"),
2392+
"DAYS_REMAINING": 14,
2393+
}
2394+
2395+
# Add challenge image if available
2396+
if challenge.image:
2397+
template_context["CHALLENGE_IMAGE_URL"] = challenge.image.url
2398+
2399+
# Email subject
2400+
subject = f"⚠️ Retention Warning: {challenge.title} - {submission_count} submissions will be deleted in 14 days"
2401+
2402+
# Send the email
2403+
return send_template_email(
2404+
recipient_email=recipient_email,
2405+
subject=subject,
2406+
template_name="challenges/retention_warning.html",
2407+
template_context=template_context,
2408+
sender_email=settings.CLOUDCV_TEAM_EMAIL,
2409+
)
2410+
2411+
23122412
@app.task
23132413
def send_retention_warning_notifications():
23142414
"""
@@ -2372,30 +2472,6 @@ def send_retention_warning_notifications():
23722472
)
23732473
continue
23742474

2375-
challenge_url = f"{settings.EVALAI_API_SERVER}/web/challenges/challenge-page/{challenge.id}"
2376-
2377-
template_data = {
2378-
"CHALLENGE_NAME": challenge.title,
2379-
"CHALLENGE_URL": challenge_url,
2380-
"SUBMISSION_COUNT": submission_count,
2381-
"RETENTION_DATE": warning_date.strftime("%B %d, %Y"),
2382-
"DAYS_REMAINING": 14,
2383-
}
2384-
2385-
if challenge.image:
2386-
template_data["CHALLENGE_IMAGE_URL"] = challenge.image.url
2387-
2388-
# Get template ID from settings
2389-
template_id = settings.SENDGRID_SETTINGS.get("TEMPLATES", {}).get(
2390-
"RETENTION_WARNING_EMAIL", None
2391-
)
2392-
2393-
if not template_id:
2394-
logger.error(
2395-
"RETENTION_WARNING_EMAIL template ID not configured in settings"
2396-
)
2397-
continue
2398-
23992475
# Get challenge host emails
24002476
try:
24012477
emails = challenge.creator.get_all_challenge_host_email()
@@ -2414,16 +2490,28 @@ def send_retention_warning_notifications():
24142490
email_sent = False
24152491
for email in emails:
24162492
try:
2417-
send_email(
2418-
sender=settings.CLOUDCV_TEAM_EMAIL,
2419-
recipient=email,
2420-
template_id=template_id,
2421-
template_data=template_data,
2422-
)
2423-
email_sent = True
2424-
logger.info(
2425-
f"Sent retention warning email to {email} for challenge {challenge.pk}"
2493+
success = send_retention_warning_email(
2494+
challenge=challenge,
2495+
recipient_email=email,
2496+
submission_count=submission_count,
2497+
warning_date=warning_date,
24262498
)
2499+
if success:
2500+
email_sent = True
2501+
logger.info(
2502+
f"Sent retention warning email to {email} for challenge {challenge.pk}"
2503+
)
2504+
else:
2505+
logger.error(
2506+
f"Failed to send retention warning email to {email} for challenge {challenge.pk}"
2507+
)
2508+
notification_errors.append(
2509+
{
2510+
"challenge_id": challenge.pk,
2511+
"email": email,
2512+
"error": "Email sending failed",
2513+
}
2514+
)
24272515
except Exception as e:
24282516
logger.error(
24292517
f"Failed to send retention warning email to {email} for challenge {challenge.pk}: {e}"
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Retention Warning - {{ CHALLENGE_NAME }}</title>
7+
<style>
8+
body {
9+
font-family: Arial, sans-serif;
10+
line-height: 1.6;
11+
color: #333;
12+
max-width: 600px;
13+
margin: 0 auto;
14+
padding: 20px;
15+
background-color: #f4f4f4;
16+
}
17+
.email-container {
18+
background-color: #ffffff;
19+
border-radius: 8px;
20+
padding: 30px;
21+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
22+
}
23+
.header {
24+
text-align: center;
25+
border-bottom: 2px solid #e74c3c;
26+
padding-bottom: 20px;
27+
margin-bottom: 30px;
28+
}
29+
.challenge-image {
30+
max-width: 200px;
31+
max-height: 120px;
32+
border-radius: 8px;
33+
margin-bottom: 15px;
34+
}
35+
.warning-icon {
36+
font-size: 48px;
37+
color: #e74c3c;
38+
margin-bottom: 10px;
39+
}
40+
.title {
41+
color: #e74c3c;
42+
font-size: 24px;
43+
font-weight: bold;
44+
margin-bottom: 10px;
45+
}
46+
.subtitle {
47+
color: #666;
48+
font-size: 16px;
49+
}
50+
.content {
51+
margin-bottom: 30px;
52+
}
53+
.highlight-box {
54+
background-color: #fff3cd;
55+
border: 1px solid #ffeaa7;
56+
border-radius: 6px;
57+
padding: 20px;
58+
margin: 20px 0;
59+
}
60+
.stats {
61+
display: flex;
62+
justify-content: space-around;
63+
margin: 25px 0;
64+
text-align: center;
65+
}
66+
.stat-item {
67+
flex: 1;
68+
padding: 15px;
69+
background-color: #f8f9fa;
70+
border-radius: 6px;
71+
margin: 0 10px;
72+
}
73+
.stat-number {
74+
font-size: 24px;
75+
font-weight: bold;
76+
color: #e74c3c;
77+
}
78+
.stat-label {
79+
font-size: 14px;
80+
color: #666;
81+
margin-top: 5px;
82+
}
83+
.cta-button {
84+
display: inline-block;
85+
background-color: #3498db;
86+
color: white;
87+
padding: 12px 30px;
88+
text-decoration: none;
89+
border-radius: 6px;
90+
font-weight: bold;
91+
margin: 20px 0;
92+
}
93+
.cta-button:hover {
94+
background-color: #2980b9;
95+
}
96+
.footer {
97+
margin-top: 30px;
98+
padding-top: 20px;
99+
border-top: 1px solid #eee;
100+
text-align: center;
101+
color: #666;
102+
font-size: 14px;
103+
}
104+
.important-note {
105+
background-color: #f8d7da;
106+
border: 1px solid #f5c6cb;
107+
border-radius: 6px;
108+
padding: 15px;
109+
margin: 20px 0;
110+
color: #721c24;
111+
}
112+
</style>
113+
</head>
114+
<body>
115+
<div class="email-container">
116+
<div class="header">
117+
{% if CHALLENGE_IMAGE_URL %}
118+
<img src="{{ CHALLENGE_IMAGE_URL }}" alt="{{ CHALLENGE_NAME }}" class="challenge-image">
119+
{% endif %}
120+
<div class="warning-icon">⚠️</div>
121+
<div class="title">Retention Warning</div>
122+
<div class="subtitle">{{ CHALLENGE_NAME }}</div>
123+
</div>
124+
125+
<div class="content">
126+
<p>Dear Challenge Host,</p>
127+
128+
<p>This is an important notification regarding your challenge <strong>{{ CHALLENGE_NAME }}</strong>.</p>
129+
130+
<div class="important-note">
131+
<strong>Action Required:</strong> Your submission artifacts will be automatically deleted in <strong>{{ DAYS_REMAINING }} days</strong> ({{ RETENTION_DATE }}) to comply with our data retention policy.
132+
</div>
133+
134+
<div class="stats">
135+
<div class="stat-item">
136+
<div class="stat-number">{{ SUBMISSION_COUNT }}</div>
137+
<div class="stat-label">Submissions Affected</div>
138+
</div>
139+
<div class="stat-item">
140+
<div class="stat-number">{{ DAYS_REMAINING }}</div>
141+
<div class="stat-label">Days Remaining</div>
142+
</div>
143+
</div>
144+
145+
<div class="highlight-box">
146+
<h3>What will be deleted?</h3>
147+
<ul>
148+
<li>Submission input files</li>
149+
<li>Output files and results</li>
150+
<li>Log files and metadata</li>
151+
<li>All associated artifacts</li>
152+
</ul>
153+
<p><strong>Note:</strong> Challenge metadata, leaderboard data, and participant information will be preserved.</p>
154+
</div>
155+
156+
<p>If you need to retain any of this data, please download it before the deletion date.</p>
157+
158+
<div style="text-align: center;">
159+
<a href="{{ CHALLENGE_URL }}" class="cta-button">View Challenge</a>
160+
</div>
161+
</div>
162+
163+
<div class="footer">
164+
<p>This is an automated notification from EvalAI.</p>
165+
<p>If you have any questions, please contact our support team.</p>
166+
<p>© 2024 EvalAI. All rights reserved.</p>
167+
</div>
168+
</div>
169+
</body>
170+
</html>

settings/common.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,6 @@
383383
"WORKER_RESTART_EMAIL": "d-3d9a474a5e2b4ac4ad5a45ba9c0b84bd",
384384
"CLUSTER_CREATION_TEMPLATE": "d-6de90fd760df4a41bb9bff1872eaab82",
385385
"WORKER_START_EMAIL": "d-debd127cab2345e789538131501ff416",
386-
"RETENTION_WARNING_EMAIL": "d-placeholder-retention-warning-template",
387386
}
388387
}
389388

0 commit comments

Comments
 (0)