Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
419e9ce
Fix ESLint and TypeScript configuration
Apr 13, 2025
f0622c6
Add Resend and React-Email as dependencies
Apr 23, 2025
0c72665
Implement basic email service
Apr 24, 2025
b84522a
Merge branch 'main' of https://github.com/cornell-dti/cu-apts into ne…
Apr 24, 2025
54594e5
Add Email Icons
Apr 24, 2025
34b06b1
Implement basic Newsletter
Apr 25, 2025
cedc3dc
Adjust TS Parsing
Apr 25, 2025
757083d
Add React-Icons
Apr 25, 2025
1f1177d
Fix parsing errors
Apr 26, 2025
ce3f320
Implement Newsletter template
Apr 27, 2025
59a8cc9
Implement Basic Newsletter (Web)
Apr 27, 2025
1220f63
Add documentation
Apr 29, 2025
2f89e7c
Implement batch sending
Apr 29, 2025
3ead424
Merge branch 'main' of https://github.com/cornell-dti/cu-apts into ne…
Apr 29, 2025
2fe143f
Add documentation in GenerateNewsletter
Apr 29, 2025
e5aeeb8
Provide documentation for email system
Apr 29, 2025
25878ce
Update email system README
Apr 29, 2025
87855a9
Add option to send email to one address
Apr 30, 2025
c9b692d
Debug property fetching
Apr 30, 2025
59248c8
Style newsletter components
Apr 30, 2025
9cc2874
Merge branch 'main' of https://github.com/cornell-dti/cu-apts into ne…
Aug 26, 2025
6655e26
Update email service and README for newsletter campaign
Aug 26, 2025
3b85370
Add mobile responsiveness and refactor email service
Sep 2, 2025
71105a2
Remove LandlordHighlight component and related props from newsletter …
Sep 17, 2025
6e5fb90
Implement May's Redesign
Sep 24, 2025
7d2e100
Implement frontend for admin newsletter management
Oct 1, 2025
a320f2e
Add warning modal for newsletter send
Oct 1, 2025
14855cf
Implement Admin Newsletter backend
Oct 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions backend/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"import/extensions": "off",
"import/no-extraneous-dependencies": "off",
"no-console": "off",
"no-shadow": "off"
"no-shadow": "off",
"react/jsx-filename-extension": ["error", { "extensions": [".tsx"] }],
"react/react-in-jsx-scope": "off"
}
}
}
8 changes: 6 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
"license": "ISC",
"dependencies": {
"@firebase/testing": "^0.20.11",
"@react-email/components": "^0.0.36",
"@react-email/render": "^1.0.6",
"@types/dotenv": "^8.2.3",
"axios": "^0.21.1",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"dotenv": "^8.6.0",
"express": "^4.17.1",
"firebase": "^8.0.1",
"firebase-admin": "^9.3.0",
Expand All @@ -36,7 +39,8 @@
"@types/cors": "^2.8.10",
"@types/express": "^4.17.11",
"@types/morgan": "^1.9.2",
"@types/node-fetch": "^2.6.12",
"@types/supertest": "^2.0.11",
"eslint-import-resolver-typescript": "^2.5.0"
}
}
}
180 changes: 180 additions & 0 deletions backend/scripts/email/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# CUApts Email System

Welcome to the CUApts email-sending system. This folder is the home for all logic and templates
related to the emails that we send to users, such as newsletters, information about events,
reminders, etc.

## Infrastructure

This system is built on top of [Resend](https://resend.com)'s email API.

## Getting Started

1. Install dependencies in the root directory:

```
npm install resend dotenv node-fetch
```

2. Set up environment variables in `.env.dev` **in the root directory of the project (`cu-apts`)**:

```
RESEND_API_KEY=your_resend_api_key # contact TPM for access
GLOBAL_FROM_NAME= CUApts # what the name of the sender will be
GLOBAL_FROM_EMAIL= cuapts.org # what the email of the sender will be
```

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

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

**Important** As many email service providers support limited CSS styling, please make sure to
use in-line CSS and web-safe fonts!

4. Update the `emailService.ts` file to use your template and customize the campaign options.

5. From the root directory, run the script:
```
ts-node backend/scripts/email/emailService.ts
```

**Important**: Before running in production, make sure to test with a small batch of recipients.

## How It Works

1. The `emailService.ts` script:

- Loads environment variables from `.env.dev`
- Initializes the Resend client with your API key
- Fetches user data from Firebase
- Fetches apartment property data from your API if needed
- Processes users into batches of 50 recipients
- Sends emails to each batch with your template

2. The system chunks the user list into batches of 50 recipients to optimize sending (Resend allows
up to 50 email addresses in a single API call).

3. Emails are sent to all batches concurrently, with progress updates printed to the console.

## Creating Templates

Templates in this system are React components that return JSX to be rendered as HTML emails.

1. Create a new TSX file in `scripts/email/templates/`.
2. Define your template structure using React components:

```tsx
import React from 'react';

interface TemplateProps {
// Define any props your template needs
customData?: string;
}

const YourTemplate: React.FC<TemplateProps> = ({ customData }) => {
return (
<BaseLayout>
{/* Your email content here */}
<h1>Hello from CUApts!</h1>
<p>{customData || 'Your default content'}</p>
</BaseLayout>
);
};

export default YourTemplate;
```

3. To use your template, update the `emailService.ts` file:

```typescript
import YourTemplate from './templates/YourTemplate';

// In the sendEmailCampaign function:
const { data, error } = await resend.emails.send({
from: `${fromName} <${fromEmail}>`,
to: toEmail,
bcc: bccEmails,
subject: subject,
react: React.createElement(YourTemplate, { customData: 'Your custom content' }),
});
```

## Creating and Sending Newsletters

The `GenerateNewsletter` template provides an easy way to send newsletters featuring apartment
listings.

### How to Configure a Newsletter Campaign:

1. In the `emailService.ts` file, update the `sendEmailCampaign` options:

```typescript
await sendEmailCampaign({
subject: 'Your Newsletter Subject',
toEmail: 'primary-recipient@example.com', // This is the main recipient
recentLandlordPropertyIDs, // Featured landlord properties
lovedPropertyIds, // Most loved properties
recentAreaPropertyIDs, // Featured area properties
});
```

2. In `templates/GenerateNewsletter.tsx`, add newsletter information.

3. For further customization:
- Extend the `GenerateNewsletter` component to accept additional props
- Modify the component structure in `Newsletter.tsx`

### User Batching

The `getUserBatches` function splits your user list into batches of 50 recipients:

```typescript
// This is handled in emailService.ts
const userBatches = await getUserBatches(50);
console.log(`Preparing to send emails to ${userBatches.length} batches of users (${50} per batch)`);
```

## Fetching User Data

User data is fetched from Firebase using the `firebase_users_loader.ts` helper module:

```typescript
import { getUserBatches, USERS } from './helpers/firebase_users_loader';

// This gives you access to all users and the ability to filter them
const validEmails = USERS.filter((user) => user.email && user.email.includes('@'));
```

## Fetching Apartment Data

Apartment data is fetched from your API using the `getPropertiesByIds` function:

```typescript
const getPropertiesByIds = async (ids: string[]): Promise<ApartmentWithId[]> => {
try {
const idParam = ids.join(',');
const response = await fetch(`${API_BASE_URL}/api/apts/${idParam}`);
// ...process and return data
} catch (error) {
console.error('Error fetching properties:', error);
return [];
}
};
```

Note that this must be done **before** running the GenerateNewsletter script, as all reactive code
is converted to pure HTML/CSS when being sent out. Thus, no data can be fetched in the body of an
email. To work around this, you must fetch property data before sending the email, and pass this
data to the newsletter template.

## Further Notes

- You **must** run the script from the root directory of the project.
- The system automatically handles large mailing lists by splitting them into batches of 50
recipients.
- Always test your emails with a small group before sending to all users.
- The system includes error handling and logging for each batch of emails sent.
- Keep your templates responsive for both desktop and mobile viewing.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions backend/scripts/email/assets/bed-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions backend/scripts/email/assets/moneyicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions backend/scripts/email/assets/review-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading