33import time
44import base64
55import os
6- from app .coursegrab .utils .constants import ALGORITHM , ANDROID , EMAIL , IOS , COURSEGRAB_FROM_EMAIL , COURSEGRAB_TO_EMAIL , MAX_BCC_SIZE
6+ from app .coursegrab .utils .constants import ALGORITHM , ANDROID , EMAIL , IOS , COURSEGRAB_FROM_EMAIL , COURSEGRAB_TO_EMAIL , MAX_RECIPIENTS_PER_SEND
77from datetime import datetime
88from hyper import HTTP20Connection
99from firebase_admin import initialize_app , messaging
@@ -177,53 +177,61 @@ def send_emails(section, emails):
177177 end_section_index = serialized_section ["section" ].find ("/" )
178178 trimmed_section_name = serialized_section ["section" ][:end_section_index ].strip ()
179179
180- # SES has a limit of 50 total recipients (to + cc + bcc) per request.
181- # We use up 1 out of 50 to send the email to ourselves. Thus we have 49 emails left to fill up with user's emails
182- # in the bcc section. So we partition the emails into chunks of size 49 max. (e.g. email_chunks = [ [49 emails], [21 emails] ])
183- email_chunks = [emails [ind :ind + MAX_BCC_SIZE ] for ind in range (0 , len (emails ), MAX_BCC_SIZE )]
180+ # Each recipient gets their own individual email (see send_single_email). We
181+ # partition into batches of MAX_RECIPIENTS_PER_SEND so SendGrid can deliver a
182+ # batch in a single API request (via one personalization per recipient).
183+ email_chunks = [emails [ind :ind + MAX_RECIPIENTS_PER_SEND ] for ind in range (0 , len (emails ), MAX_RECIPIENTS_PER_SEND )]
184184
185185 try :
186186 # Send separate email for each chunk
187187 for chunk in email_chunks :
188188 send_single_email (subject_code , course_num , trimmed_section_name , chunk )
189+ # Send one monitoring copy of this notification to our own inbox so we keep
190+ # a running record of every notification that goes out.
191+ send_single_email (subject_code , course_num , trimmed_section_name , [COURSEGRAB_TO_EMAIL ])
189192 except Exception as e :
190193 print ("Error while sending email notifications:" , e )
191194
192195
193- def send_single_email (subject_code , course_num , trimmed_section_name , email_chunk ):
194- """Send email notification to single chunk of user emails (max 49 bcc emails)"""
196+ def send_single_email (subject_code , course_num , trimmed_section_name , email_chunk ):
197+ """Send an individual notification email to each recipient in the chunk.
198+
199+ Each recipient is the sole To: address (a transactional 1:1 pattern) instead of
200+ being BCC'd on one shared bulk message. This scores far better with strict inbox
201+ providers (e.g. Microsoft/Outlook) and keeps recipients' addresses private from
202+ one another.
203+ """
195204 course_name_full = f"{ subject_code } { course_num } { trimmed_section_name } "
196205 subject = f"{ course_name_full } is Now Open"
197206 html_content = email_body .replace ("COURSE_NAME_NUM" , course_name_full )
198207
199208 if EMAIL_PROVIDER == "ses" :
200- try :
201- ses_client .send_email (
202- Source = f"CourseGrab by AppDev <{ COURSEGRAB_FROM_EMAIL } >" ,
203- Destination = {
204- "ToAddresses" : [COURSEGRAB_TO_EMAIL ],
205- "BccAddresses" : list (email_chunk ), # Add users' emails to bcc
206- },
207- Message = {
208- "Subject" : {"Data" : subject },
209- "Body" : {"Html" : {"Data" : html_content }},
210- },
211- )
212- except Exception as e :
213- print (f"Error sending email: { e } " )
209+ # SES has no multi-recipient personalization; send one email per recipient.
210+ for recipient in email_chunk :
211+ try :
212+ ses_client .send_email (
213+ Source = f"CourseGrab by AppDev <{ COURSEGRAB_FROM_EMAIL } >" ,
214+ Destination = {"ToAddresses" : [recipient ]},
215+ Message = {
216+ "Subject" : {"Data" : subject },
217+ "Body" : {"Html" : {"Data" : html_content }},
218+ },
219+ )
220+ except Exception as e :
221+ print (f"Error sending email to { recipient } : { e } " )
214222 else : # sendgrid
223+ # One request with a separate personalization per recipient: SendGrid
224+ # delivers an individual email to each, and recipients never see each other.
215225 message = Mail (
216226 from_email = Email (COURSEGRAB_FROM_EMAIL , "CourseGrab by AppDev" ),
217227 subject = subject ,
218228 html_content = html_content ,
219229 )
220- personalization = Personalization ()
221- personalization .add_to (To (COURSEGRAB_TO_EMAIL ))
222- for bcc_email in email_chunk : # Add users' emails to bcc
223- personalization .add_bcc (Email (bcc_email ))
224- message .add_personalization (personalization )
230+ for recipient in email_chunk : # one To: per recipient
231+ personalization = Personalization ()
232+ personalization .add_to (To (recipient ))
233+ message .add_personalization (personalization )
225234 try :
226235 sendgrid_client .send (message )
227236 except Exception as e :
228237 print (f"Error sending email: { e } " )
229-
0 commit comments