Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ env:

on:
push:
branches:
- main
# Any PR
pull_request:

permissions:
Expand Down
13 changes: 13 additions & 0 deletions collection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,16 @@ Other tools have their own package.jsons as that was how they were originally se
# Docker

To build the docker image, run `npx nx container collection` from the _root_ of the repo.

# What `emails/` for?

This is for sending reminder emails to those who haven't collected their merch yet.

Usage:

```bash
cd emails
npx tsx getUncollectedPeople.ts # outputs a JSON file to output/reminders.json
docsoc-mailmerge generate nunjucks ./data/reminders.json ./templates/reminder.njk -s json -o output -n reminders
# then send (check README in emails folder)!
```
18 changes: 18 additions & 0 deletions collection/emails/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Fill these in to send emails
DOCSOC_SMTP_SERVER=smtp-mail.outlook.com
DOCSOC_SMTP_PORT=587
DOCSOC_OUTLOOK_USERNAME=
# Password to docsoc email
DOCSOC_OUTLOOK_PASSWORD=

# Optional: Fill these in to uplod drafts
# You will need to create an app registration in Entra ID, restricted to the organisation,
# And grant it the following permissions:
# - Mail.ReadWrite
# - User.Read
MS_ENTRA_CLIENT_ID=
MS_ENTRA_CLIENT_SECRET=
MS_ENTRA_TENANT_ID=

# DB URL for use by getUncollectedPeople
COLLECTION_DATABASE_URL=
9 changes: 9 additions & 0 deletions collection/emails/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
attachments/*
!attachments/.gitkeep

data/*
!data/.gitkeep
!data/example.csv

output/*
!output/.gitkeep
59 changes: 59 additions & 0 deletions collection/emails/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
To get started:

1. Put your own CSV in the `data` folder, with at the minimum `to` and `subject` columns.
1. You can also add `cc` and `bcc` columns (to use them you will need to pass the correct CLI option though)
2. `to`, `cc`, and `bcc` can be a space-separated list of emails.
3. You can add any other columns you like, and they will be available in the template.
4. For attachments, add a column with the name `attachment` with a singular path to the file to attach relative to th workspace root (e.g. `./attachments/image1.jpg`).
1. Or, pass the same attachment to every email using the `-a` flag to `generate`
5. For multiple attachments, have separate columns e.g. `attachment1`, `attachment2`, etc.
6. See `data/example.csv` for an example.
2. Put your own nunjucks markdown email template in the `templates` folder.
1. You can also edit the default `wrapper.html.njk` file - this is what the markdown HTML will be wrapped in when sending it. It muat _always_ include a `{{ content }}` tag, which will be replaced with the markdown HTML.
3. Fill in the `.env` file with your email credentials.

Then run the following commands:

```bash
docsoc-mailmerge generate nunjucks ./data/my-data.csv ./templates/my-template.md.njk -o ./output --htmlTemplate ./templates/wrapper.html.njk
# make some edits to the outputs and regenerate them:
docsoc-mailmerge regenerate ./output/<runname>
# review them, then send:
docsoc-mailmerge send ./output/<runname>
```

The CLI tool has many options - use `--help` to see them all:

```bash
docsoc-mailmerge generate nunjucks --help
docsoc-mailmerge regenerate --help
docsoc-mailmerge send --help
```

## What happen when you generate

1. Each record in the CSV will result in 3 files in `./output/<runname>`: an editable markdown file to allow you to modify the email, a HTML rendering of the markdown that you should not edit, and a `.json` metadata file
2. The HTML files, which is what is actually sent, can be regenerated after edting the markdown files with `regenerate` command (see below)
3. If you want to edit the to address or subject after this point you will need to edit the JSON files; csv edits are ignored. If you edit the CSV, delete all outputs and run generate again.

## If the .env file is missing

Use this template:

```bash
# Fill these in to send emails
DOCSOC_SMTP_SERVER=smtp-mail.outlook.com
DOCSOC_SMTP_PORT=587
DOCSOC_OUTLOOK_USERNAME=
# Password to docsoc email
DOCSOC_OUTLOOK_PASSWORD=

# Optional: Fill these in to uplod drafts
# You will need to create an app registration in Entra ID, restricted to the organisation,
# And grant it the following permissions:
# - Mail.ReadWrite
# - User.Read
MS_ENTRA_CLIENT_ID=
MS_ENTRA_CLIENT_SECRET=
MS_ENTRA_TENANT_ID=
```
Empty file.
Empty file added collection/emails/data/.gitkeep
Empty file.
2 changes: 2 additions & 0 deletions collection/emails/data/example.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name,to,subject,attachment1,attachment2,bcc,cc
Example Person,[email protected] [email protected],Example subject,./attachments/image1.jpg,./attachments/image2.jpg,[email protected],[email protected] [email protected]
90 changes: 90 additions & 0 deletions collection/emails/getUncollectedPeople.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* Outputs a JSON file with the students who have not collected their merch, for use by the mailmerge tool
* to send a reminder to these people.
*
* Created with co-pilot.
*/
import { PrismaClient } from "@prisma/client";
// Load .env file ".env"
import dotenv from "dotenv";
import fs from "fs/promises";

dotenv.config();

const prisma = new PrismaClient();

interface OutputRecord {
to: string;
name: string;
shortcode: string;
itemsToCollect: {
rootitem: string;
variant: string;
quantity: number;
}[];
subject: string;
}

async function getUncollectedPeople() {
const uncollectedOrders = await prisma.orderItem.findMany({
where: { collected: false },
include: {
Order: {
include: {
ImperialStudent: true,
},
},
Variant: {
include: {
RootItem: true,
},
},
},
});

const outputRecords: OutputRecord[] = [];

const studentMap = new Map<string, OutputRecord>();

for (const orderItem of uncollectedOrders) {
const student = orderItem.Order.ImperialStudent;
const studentKey = student.email;

// Filter out if rootitem is "Pride Lanyard"
// (in 2024 we started handing out these lanyard to whoever after the summer collection so
/// it was likely that anyway who didn't collect it during merch collections got them during the random hand outs we did)
if (orderItem.Variant.RootItem.name === "Pride Lanyard") {
console.log(`Skipping ${studentKey} for Pride Lanyard`);
continue;
}

if (!studentMap.has(studentKey)) {
studentMap.set(studentKey, {
to: student.email,
name: `${student.firstName} ${student.lastName}`,
shortcode: student.shortcode,
itemsToCollect: [],
subject: "Collect your remaining DoCSoc Summer Merch",
});
}

const studentRecord = studentMap.get(studentKey)!;
studentRecord.itemsToCollect.push({
rootitem: orderItem.Variant.RootItem.name,
variant: orderItem.Variant.variantName,
quantity: orderItem.quantity,
});
}

outputRecords.push(...studentMap.values());

await fs.writeFile("data/reminders.json", JSON.stringify(outputRecords, null, 2));
}

getUncollectedPeople()
.catch((e) => {
console.error(e);
})
.finally(async () => {
await prisma.$disconnect();
});
Loading