diff --git a/.yarn/cache/@sendgrid-client-npm-7.4.6-d164f28ba6-45554a1c1f.zip b/.yarn/cache/@sendgrid-client-npm-7.4.6-d164f28ba6-45554a1c1f.zip
deleted file mode 100644
index 462e67b8..00000000
Binary files a/.yarn/cache/@sendgrid-client-npm-7.4.6-d164f28ba6-45554a1c1f.zip and /dev/null differ
diff --git a/.yarn/cache/@sendgrid-helpers-npm-7.4.6-dbf1471e5d-64d6e48908.zip b/.yarn/cache/@sendgrid-helpers-npm-7.4.6-dbf1471e5d-64d6e48908.zip
deleted file mode 100644
index 66a0e26d..00000000
Binary files a/.yarn/cache/@sendgrid-helpers-npm-7.4.6-dbf1471e5d-64d6e48908.zip and /dev/null differ
diff --git a/.yarn/cache/@sendgrid-mail-npm-7.4.6-99cce529bf-e923fc3bd4.zip b/.yarn/cache/@sendgrid-mail-npm-7.4.6-99cce529bf-e923fc3bd4.zip
deleted file mode 100644
index df8dd097..00000000
Binary files a/.yarn/cache/@sendgrid-mail-npm-7.4.6-99cce529bf-e923fc3bd4.zip and /dev/null differ
diff --git a/.yarn/cache/axios-npm-0.21.4-e278873748-44245f24ac.zip b/.yarn/cache/axios-npm-0.21.4-e278873748-44245f24ac.zip
new file mode 100644
index 00000000..756d87a5
Binary files /dev/null and b/.yarn/cache/axios-npm-0.21.4-e278873748-44245f24ac.zip differ
diff --git a/.yarn/cache/postmark-npm-2.7.8-84671dcea9-a4882fcf28.zip b/.yarn/cache/postmark-npm-2.7.8-84671dcea9-a4882fcf28.zip
new file mode 100644
index 00000000..6a878e35
Binary files /dev/null and b/.yarn/cache/postmark-npm-2.7.8-84671dcea9-a4882fcf28.zip differ
diff --git a/lib/mail/components.tsx b/lib/mail/components.tsx
index 6b3ad365..24813b3c 100644
--- a/lib/mail/components.tsx
+++ b/lib/mail/components.tsx
@@ -283,9 +283,9 @@ export function Footer(): JSX.Element {
- If this is spam, let me know at{' '}
-
- nicholas@tutorbook.org
+ If this is annoying, you can always{' '}
+
+ unsubscribe
diff --git a/lib/mail/login.tsx b/lib/mail/login.tsx
index 235e40b0..398f85d4 100644
--- a/lib/mail/login.tsx
+++ b/lib/mail/login.tsx
@@ -4,6 +4,7 @@ import send from 'lib/mail/send';
export default function mail(email: string, location: string, link: string): Promise {
return send({
to: [{ email }],
+ stream: 'login',
subject: `Login Confirmation (${location})`,
template: (
diff --git a/lib/mail/meetings/1hr.tsx b/lib/mail/meetings/1hr.tsx
index d213c151..731ec3f3 100644
--- a/lib/mail/meetings/1hr.tsx
+++ b/lib/mail/meetings/1hr.tsx
@@ -7,6 +7,7 @@ export default function mail(meeting: Meeting): Promise {
const to = meeting.people.filter((p) => p.email);
return send({
to,
+ stream: 'meeting-1hr',
subject: `Reminder - ${meeting.subjects[0].name} lesson today`,
template: (
diff --git a/lib/mail/meetings/24hr.tsx b/lib/mail/meetings/24hr.tsx
index 933f5d49..1c26588b 100644
--- a/lib/mail/meetings/24hr.tsx
+++ b/lib/mail/meetings/24hr.tsx
@@ -7,6 +7,7 @@ export default function mail(meeting: Meeting): Promise {
const to = meeting.people.filter((p) => p.email);
return send({
to,
+ stream: 'meeting-24hr',
subject: `Reminder - ${meeting.subjects[0].name} lesson tomorrow`,
template: (
diff --git a/lib/mail/meetings/create.tsx b/lib/mail/meetings/create.tsx
index a2597551..03c84149 100644
--- a/lib/mail/meetings/create.tsx
+++ b/lib/mail/meetings/create.tsx
@@ -8,6 +8,7 @@ export default function mail(meeting: Meeting): Promise {
return send({
to,
cc: meeting.creator,
+ stream: 'meeting-created',
subject: `${meeting.creator.firstName} booked a meeting with you`,
template: (
diff --git a/lib/mail/meetings/delete.tsx b/lib/mail/meetings/delete.tsx
index fac42dee..1c3b77dc 100644
--- a/lib/mail/meetings/delete.tsx
+++ b/lib/mail/meetings/delete.tsx
@@ -9,6 +9,7 @@ export default function mail(meeting: Meeting, deleter: User): Promise {
return send({
to,
cc: deleter,
+ stream: 'meeting-deleted',
subject: `${deleter.firstName} canceled a meeting with you`,
template: (
diff --git a/lib/mail/meetings/recur.tsx b/lib/mail/meetings/recur.tsx
index c409f7c9..b2918578 100644
--- a/lib/mail/meetings/recur.tsx
+++ b/lib/mail/meetings/recur.tsx
@@ -7,6 +7,7 @@ export default function mail(meeting: Meeting): Promise {
const to = meeting.people.filter((p) => p.email);
return send({
to,
+ stream: 'meeting-recur',
subject: `Enjoy your ${meeting.subjects[0].name.toLowerCase()} lesson? Make it recurring`,
template: (
diff --git a/lib/mail/meetings/update.tsx b/lib/mail/meetings/update.tsx
index a0af7fe3..af25e67c 100644
--- a/lib/mail/meetings/update.tsx
+++ b/lib/mail/meetings/update.tsx
@@ -9,6 +9,7 @@ export default function mail(meeting: Meeting, updater: User): Promise {
return send({
to,
cc: updater,
+ stream: 'meeting-updated',
subject: `${updater.firstName} updated a meeting with you`,
template: (
diff --git a/lib/mail/request.tsx b/lib/mail/request.tsx
index 735bb8af..c794a416 100644
--- a/lib/mail/request.tsx
+++ b/lib/mail/request.tsx
@@ -15,6 +15,7 @@ export default function mail(subjects: Subject[], description: string, user: Use
return send({
to: admins.filter((p) => p.email),
cc: user,
+ stream: 'request',
subject: `Request - ${user.firstName} for ${join(subjects)}`,
template: (
diff --git a/lib/mail/send.ts b/lib/mail/send.ts
index d224f3a7..cb9c9e84 100644
--- a/lib/mail/send.ts
+++ b/lib/mail/send.ts
@@ -1,34 +1,47 @@
-import { MailData } from '@sendgrid/helpers/classes/mail';
import { ReactElement } from 'react';
-import mail from '@sendgrid/mail';
+import { ServerClient } from 'postmark';
import { renderToStaticMarkup } from 'react-dom/server';
import to from 'await-to-js';
import { APIError } from 'lib/model/error';
-export type Email = {
+export type EmailStream =
+ | 'login'
+ | 'request'
+ | 'user-created'
+ | 'meeting-created'
+ | 'meeting-updated'
+ | 'meeting-deleted'
+ | 'meeting-24hr'
+ | 'meeting-1hr'
+ | 'meeting-recur';
+
+export interface Email {
+ subject: string;
+ stream: EmailStream;
template: ReactElement;
- replyTo?: { name?: string; email: string };
- from?: { name?: string; email: string };
to: { name?: string; email: string }[];
-} & Omit;
+ cc?: { name?: string; email: string };
+ replyTo?: { name?: string; email: string };
+}
export default async function send(email: Email): Promise {
if (['development', 'test'].includes(process.env.APP_ENV as string)) return;
- if (typeof process.env.SENDGRID_API_KEY !== 'string') {
- throw new APIError('Cannot send emails without SendGrid API key.');
- } else {
- mail.setApiKey(process.env.SENDGRID_API_KEY);
- const [e] = await to(
- mail.send({
- ...email,
- from: { name: 'Tutorbook', email: 'team@tutorbook.org' },
- bcc: { name: 'Tutorbook', email: 'team@tutorbook.org' },
- replyTo: email.replyTo?.email ? email.replyTo : undefined,
- html: renderToStaticMarkup(email.template),
- to: email.to.filter((p) => p.email),
- })
- );
- if (e) throw new APIError(`${e.name} sending email: ${e.message}`, 500);
- }
+ const key = email.stream.includes('meeting') ?
+ process.env.POSTMARK_MTG_KEY :
+ process.env.POSTMARK_API_KEY;
+ const client = new ServerClient(key as string);
+ const [e] = await to(
+ client.sendEmail({
+ From: 'team@tutorbook.org',
+ Bcc: 'team@tutorbook.org',
+ To: email.to.map((u) => u.email).join(', '),
+ Cc: email.cc?.email,
+ ReplyTo: email.replyTo?.email,
+ Subject: email.subject,
+ HtmlBody: renderToStaticMarkup(email.template),
+ MessageStream: email.stream,
+ })
+ );
+ if (e) throw new APIError(`${e.name} sending email: ${e.message}`, 500);
}
diff --git a/lib/mail/users/create.tsx b/lib/mail/users/create.tsx
index 11103c1c..c399ad76 100644
--- a/lib/mail/users/create.tsx
+++ b/lib/mail/users/create.tsx
@@ -7,6 +7,7 @@ import send from 'lib/mail/send';
export default function mail(user: User, org: Org, admins: User[]): Promise {
return send({
to: admins.filter((p) => p.email),
+ stream: 'user-created',
subject: `${user.firstName} signed up on Tutorbook`,
template: (
diff --git a/package.json b/package.json
index 8257cfba..31a5ef5d 100644
--- a/package.json
+++ b/package.json
@@ -126,9 +126,6 @@
"@rmwc/textfield": "^6.1.4",
"@rmwc/tooltip": "^6.1.4",
"@segment/analytics.js-core": "^4.1.11",
- "@sendgrid/client": "^7.4.6",
- "@sendgrid/helpers": "^7.4.6",
- "@sendgrid/mail": "^7.4.6",
"@supabase/supabase-js": "^1.25.0",
"accept-language-parser": "^1.5.0",
"algoliasearch": "^4.10.5",
@@ -149,6 +146,7 @@
"nprogress": "^0.2.0",
"password-generator": "^2.3.2",
"phone": "^3.1.6",
+ "postmark": "^2.7.8",
"prop-types": "^15.7.2",
"re-resizable": "^6.5.4",
"react": "^17.0.2",
diff --git a/yarn.lock b/yarn.lock
index b445f87a..e51a1438 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3737,35 +3737,6 @@ __metadata:
languageName: node
linkType: hard
-"@sendgrid/client@npm:^7.4.6":
- version: 7.4.6
- resolution: "@sendgrid/client@npm:7.4.6"
- dependencies:
- "@sendgrid/helpers": ^7.4.6
- axios: ^0.21.1
- checksum: 45554a1c1f0f31758df31637c810ded4d5d993d1846413c633d80ee89531ba31aa2e85704290419d8529b751f498ea892a1c6e4c7f43d2c0dbffa8db6e609cdb
- languageName: node
- linkType: hard
-
-"@sendgrid/helpers@npm:^7.4.6":
- version: 7.4.6
- resolution: "@sendgrid/helpers@npm:7.4.6"
- dependencies:
- deepmerge: ^4.2.2
- checksum: 64d6e489087ff90e3f8fee2ffb07876911140bdebe7713be129f8438329c8bfbedff2f87a0e89b1043e000780ca706b25a48cb86248c5d2b2f46b6c2c828c054
- languageName: node
- linkType: hard
-
-"@sendgrid/mail@npm:^7.4.6":
- version: 7.4.6
- resolution: "@sendgrid/mail@npm:7.4.6"
- dependencies:
- "@sendgrid/client": ^7.4.6
- "@sendgrid/helpers": ^7.4.6
- checksum: e923fc3bd4ce84abfb5294c580dc6066c92f9b9159829685e9fe5eb070fbdd887a734618ce472875c71077a12619b5bb0582bceb04c82627e9ca699c51cadcde
- languageName: node
- linkType: hard
-
"@sideway/address@npm:^4.1.0":
version: 4.1.2
resolution: "@sideway/address@npm:4.1.2"
@@ -5244,6 +5215,15 @@ __metadata:
languageName: node
linkType: hard
+"axios@npm:^0.21.4":
+ version: 0.21.4
+ resolution: "axios@npm:0.21.4"
+ dependencies:
+ follow-redirects: ^1.14.0
+ checksum: 44245f24ac971e7458f3120c92f9d66d1fc695e8b97019139de5b0cc65d9b8104647db01e5f46917728edfc0cfd88eb30fc4c55e6053eef4ace76768ce95ff3c
+ languageName: node
+ linkType: hard
+
"axobject-query@npm:^2.2.0":
version: 2.2.0
resolution: "axobject-query@npm:2.2.0"
@@ -7822,7 +7802,7 @@ __metadata:
languageName: node
linkType: hard
-"deepmerge@npm:^4.0.0, deepmerge@npm:^4.2.2":
+"deepmerge@npm:^4.0.0":
version: 4.2.2
resolution: "deepmerge@npm:4.2.2"
checksum: a8c43a1ed8d6d1ed2b5bf569fa4c8eb9f0924034baf75d5d406e47e157a451075c4db353efea7b6bcc56ec48116a8ce72fccf867b6e078e7c561904b5897530b
@@ -15099,6 +15079,15 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
+"postmark@npm:^2.7.8":
+ version: 2.7.8
+ resolution: "postmark@npm:2.7.8"
+ dependencies:
+ axios: ^0.21.4
+ checksum: a4882fcf28bf31312a6440c7b23bd5654df0ba2adee60009d0c708c816a16c3f730185b39ba1a8d0e7bd23706cdb6eb75772ad8ab9aa5916c5774e951f6395ec
+ languageName: node
+ linkType: hard
+
"prebuild-install@npm:^6.1.4":
version: 6.1.4
resolution: "prebuild-install@npm:6.1.4"
@@ -18394,9 +18383,6 @@ resolve@^2.0.0-next.3:
"@rmwc/textfield": ^6.1.4
"@rmwc/tooltip": ^6.1.4
"@segment/analytics.js-core": ^4.1.11
- "@sendgrid/client": ^7.4.6
- "@sendgrid/helpers": ^7.4.6
- "@sendgrid/mail": ^7.4.6
"@supabase/supabase-js": ^1.25.0
"@types/accept-language-parser": ^1.5.2
"@types/analytics-node": ^3.1.5
@@ -18455,6 +18441,7 @@ resolve@^2.0.0-next.3:
nyc: ^15.1.0
password-generator: ^2.3.2
phone: ^3.1.6
+ postmark: ^2.7.8
prettier: ^2.3.2
pretty-quick: ^3.1.1
prompt-sync: ^4.2.0