Skip to content

Commit 6655e26

Browse files
author
Lauren Pothuru
committed
Update email service and README for newsletter campaign
- Abstract unnecessary comments and clean up code - Fix environment variable handling in firebase config - Update README for clarity and formatting
1 parent 9cc2874 commit 6655e26

2 files changed

Lines changed: 96 additions & 74 deletions

File tree

backend/scripts/email/README.MD

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ This system is built on top of [Resend](https://resend.com)'s email API.
1212

1313
1. Install dependencies in the root directory:
1414

15-
```bash
15+
```
1616
npm install resend dotenv node-fetch
1717
```
1818

@@ -26,15 +26,18 @@ This system is built on top of [Resend](https://resend.com)'s email API.
2626

2727
**Never commit this file!** (Should already be in `.gitignore`.)
2828

29-
3. Create a new template in `scripts/email/templates/` or modify the existing
29+
3. Create a new email template in `scripts/email/templates/` or modify the existing
3030
`GenerateNewsletter.tsx` template. Make sure to test out the formatting in multiple email
3131
services (ex: gmail vs apple mail). Each service will result in slightly different outputs, so
3232
ensure that your template is consistent throughout.
3333

34+
**Important** As many email service providers support limited CSS styling, please make sure to
35+
use in-line CSS and web-safe fonts!
36+
3437
4. Update the `emailService.ts` file to use your template and customize the campaign options.
3538

3639
5. From the root directory, run the script:
37-
```bash
40+
```
3841
ts-node backend/scripts/email/emailService.ts
3942
```
4043

backend/scripts/email/emailService.ts

Lines changed: 90 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const sendEmailCampaign = async (options: EmailCampaignOptions = {}): Promise<vo
5353
return;
5454
}
5555

56-
// const { API_BASE_URL } = process.env;
56+
const { API_BASE_URL } = process.env;
5757

5858
/**
5959
* getPropertiesByIds
@@ -81,17 +81,7 @@ const sendEmailCampaign = async (options: EmailCampaignOptions = {}): Promise<vo
8181
}
8282
};
8383

84-
// try {
85-
// console.log(`Total users available in database: ${USERS.length}`);
86-
// const validEmails = USERS.filter((user) => user.email && user.email.includes('@'));
87-
// console.log(`Valid email addresses: ${validEmails.length}`);
88-
89-
// if (validEmails.length === 0) {
90-
// console.error('No valid email addresses found!');
91-
// return;
92-
// }
93-
94-
// loads chosen properties
84+
// Loads chosen properties
9585
const recentLandlordProperties = options.recentLandlordPropertyIDs
9686
? await getPropertiesByIds(options.recentLandlordPropertyIDs)
9787
: [];
@@ -108,71 +98,100 @@ const sendEmailCampaign = async (options: EmailCampaignOptions = {}): Promise<vo
10898

10999
const resend = new Resend(apiKey);
110100

111-
// const userBatches = await getUserBatches(50);
112-
// console.log(
113-
// `Preparing to send emails to ${userBatches.length} batches of users (${50} per batch)`
114-
// );
115-
116-
// const emailPromises = userBatches.map(async (batch, i) => {
117-
// const bccEmails = batch.map((user) => user.email);
118-
// console.log(
119-
// `Preparing batch ${i + 1}/${userBatches.length} with ${bccEmails.length} recipients`
120-
// );
121-
122-
// const { data, error } = await resend.emails.send({
123-
// from: `${fromName} <${fromEmail}>`,
124-
// to: toEmail,
125-
// // bcc: bccEmails,
126-
// subject,
127-
// react: React.createElement(GenerateNewsletter, {
128-
// recentLandlordProperties,
129-
// lovedProperties,
130-
// recentAreaProperties,
131-
// }),
132-
// });
133-
134-
// if (error) {
135-
// console.error(`Error sending batch ${i + 1}:`, error);
136-
// } else {
137-
// console.log(`Batch ${i + 1} sent successfully! ID:`, data?.id || 'no ID returned');
138-
// }
139-
// });
140-
141-
// await Promise.all(emailPromises);
142-
// console.log('All email batches sent successfully!');
143-
// } catch (err) {
144-
// console.error('Exception when sending emails:', err);
145-
// throw err;
146-
// }
147-
148-
/** Sends an email to one person (useful for testing email templates).
149-
* To use, uncomment code below, comment out lines 82-86 and 108-143, edit info below,
101+
/**
102+
* BATCH PROCESSING AND EMAIL SENDING
103+
*
104+
* Processes users in batches and sends emails concurrently:
105+
* - Creates batches of 50 users each using getUserBatches()
106+
* - Maps over batches to send emails in parallel
107+
* - Uses BCC to hide recipient emails from each other
108+
* - Includes error handling and progress logging for each batch
109+
* - Waits for all batches to complete using Promise.all()
110+
*
111+
* To use, uncomment line 191, comment out line 192, and run the file as normal.
112+
*/
113+
const sendBatchEmail = async () => {
114+
try {
115+
console.log(`Total users available in database: ${USERS.length}`);
116+
const validEmails = USERS.filter((user) => user.email && user.email.includes('@'));
117+
console.log(`Valid email addresses: ${validEmails.length}`);
118+
119+
if (validEmails.length === 0) {
120+
console.error('No valid email addresses found!');
121+
return;
122+
}
123+
124+
const userBatches = await getUserBatches(50);
125+
console.log(
126+
`Preparing to send emails to ${userBatches.length} batches of users (${50} per batch)`
127+
);
128+
129+
const emailPromises = userBatches.map(async (batch, i) => {
130+
const bccEmails = batch.map((user) => user.email);
131+
console.log(
132+
`Preparing batch ${i + 1}/${userBatches.length} with ${bccEmails.length} recipients`
133+
);
134+
135+
const { data, error } = await resend.emails.send({
136+
from: `${fromName} <${fromEmail}>`,
137+
to: toEmail,
138+
// bcc: bccEmails,
139+
subject,
140+
react: React.createElement(GenerateNewsletter, {
141+
recentLandlordProperties,
142+
lovedProperties,
143+
recentAreaProperties,
144+
}),
145+
});
146+
147+
if (error) {
148+
console.error(`Error sending batch ${i + 1}:`, error);
149+
} else {
150+
console.log(`Batch ${i + 1} sent successfully! ID:`, data?.id || 'no ID returned');
151+
}
152+
});
153+
154+
await Promise.all(emailPromises);
155+
console.log('All email batches sent successfully!');
156+
} catch (err) {
157+
console.error('Exception when sending emails:', err);
158+
throw err;
159+
}
160+
};
161+
162+
/**
163+
* SINGLE TEST EMAIL SENDING
164+
* Sends an email to one person (useful for testing email templates).
165+
* To use, uncomment line 192, comment out line 191, edit info below,
150166
* and run the file as normal.
151167
*/
152-
try {
153-
// In your main file
154-
const { data, error } = await resend.emails.send({
155-
from: 'updates@cuapts.org',
156-
to: 'lsp75@cornell.edu',
157-
subject,
158-
react: React.createElement(GenerateNewsletter, {
159-
recentLandlordProperties,
160-
lovedProperties,
161-
recentAreaProperties,
162-
}),
163-
});
164-
if (error) {
165-
console.error('Error sending email:', error);
166-
} else {
167-
console.log('Email sent successfully! ID:', data ? data.id : ' no ID returned.');
168+
const sendSingleTestEmail = async () => {
169+
try {
170+
const { data, error } = await resend.emails.send({
171+
from: 'updates@cuapts.org',
172+
to: 'laurenpothuru@gmail.com',
173+
subject,
174+
react: React.createElement(GenerateNewsletter, {
175+
recentLandlordProperties,
176+
lovedProperties,
177+
recentAreaProperties,
178+
}),
179+
});
180+
if (error) {
181+
console.error('Error sending email:', error);
182+
} else {
183+
console.log('Email sent successfully! ID:', data ? data.id : ' no ID returned.');
184+
}
185+
} catch (err) {
186+
console.error('Exception when sending email:', err);
168187
}
169-
} catch (err) {
170-
console.error('Exception when sending email:', err);
171-
}
188+
};
189+
190+
// sendBatchEmail();
191+
sendSingleTestEmail();
172192
};
173193

174194
/**
175-
* main
176195
* Entry point function that executes the email campaign with default settings.
177196
* Handles logging and error handling for the campaign process.
178197
*

0 commit comments

Comments
 (0)