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