diff --git a/contracts/package.json b/contracts/package.json index f75f51420..6560a461a 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -65,6 +65,7 @@ "@nomiclabs/hardhat-ethers": "^2.2.3", "@nomiclabs/hardhat-solhint": "^3.0.1", "@openzeppelin/contracts": "^4.9.5", + "@sendgrid/mail": "^8.1.1", "@typechain/ethers-v5": "^11.1.2", "@typechain/hardhat": "^7.0.0", "@types/chai": "^4.3.11", diff --git a/contracts/scripts/sendEmail.ts b/contracts/scripts/sendEmail.ts new file mode 100644 index 000000000..ebece4112 --- /dev/null +++ b/contracts/scripts/sendEmail.ts @@ -0,0 +1,147 @@ +import sgMail, { MailService } from "@sendgrid/mail"; + +export const createCourtMailer = (_sendGridClient: MailService, _frontEndUrl: string, _unsubscribeLink: string) => { + const sgMail = _sendGridClient; + const frontEndUrl = _frontEndUrl; + const unsubscribeLink = _unsubscribeLink; // should be a link to the court's notification settings page + const defaults = { + from: "appsupport@kleros.builders", + replyTo: "appsupport@kleros.io", + }; + + const getCasesUrl = (caseId: string) => `${frontEndUrl}/cases/${caseId}`; + + const notifySignUp = (to: string, onboardingUrl: string) => { + const templateId = "d-409cfb5a26e1486fbaae8ca7975bf36b "; + return send({ + to, + templateId, + dynamicTemplateData: { + onboardingUrl, + unsubscribeLink, + }, + }); + }; + + const notifyDrawnJuror = (to: string, caseId: string) => { + const templateId = "d-db25f02c1b73499d90743c6cf2236890"; + return send({ + to, + templateId, + dynamicTemplateData: { + caseId, + caseUrl: getCasesUrl(caseId), + unsubscribeLink, + }, + }); + }; + + const notifyReminderToVote = (to: string, caseId: string) => { + const templateId = "d-c44e4a4e6c134008a7674fd3d778fee0"; + return send({ + to, + templateId, + dynamicTemplateData: { + caseId, + caseUrl: getCasesUrl(caseId), + unsubscribeLink, + }, + }); + }; + + const notifyAppealPeriod = (to: string, caseId: string, winningChoice: string, appealEnd: string) => { + const templateId = "d-3c912511364d4a2cb95eb97dd776019c"; + return send({ + to, + templateId, + dynamicTemplateData: { + caseId, + winningChoice, + appealEnd, + caseUrl: getCasesUrl(caseId), + unsubscribeLink, + }, + }); + }; + + const notifyAppealed = (to: string, caseId: string, votingEnd: string) => { + const templateId = "d-b610f2a021ad47f583449d74b27c7716"; + return send({ + to, + templateId, + dynamicTemplateData: { + caseId, + votingEnd, + caseUrl: getCasesUrl(caseId), + unsubscribeLink, + }, + }); + }; + + const notifyWinner = (to: string, caseId: string, rewards: string, ruling: string) => { + const templateId = "d-ec75665e848e4e30b0678eda55ea0a2b"; + return send({ + to, + templateId, + dynamicTemplateData: { + caseId, + rewards, + ruling, + caseUrl: getCasesUrl(caseId), + unsubscribeLink, + }, + }); + }; + + const notifyLoser = (to: string, caseId: string, penalty: string, ruling: string) => { + const templateId = "d-248456f545c44a6f9496c4ce4e1fab4a"; + return send({ + to, + templateId, + dynamicTemplateData: { + caseId, + penalty, + ruling, + caseUrl: getCasesUrl(caseId), + unsubscribeLink, + }, + }); + }; + + // https://github.com/sendgrid/sendgrid-nodejs/blob/main/docs/use-cases/transactional-templates.md + // https://docs.sendgrid.com/api-reference/mail-send/mail-send#dynamic-transactional-templates-and-handlebars + const send = (msg: any) => sgMail.send({ ...defaults, ...msg }); + + return { + notifySignUp, + notifyDrawnJuror, + notifyReminderToVote, + notifyAppealPeriod, + notifyAppealed, + notifyWinner, + send, + }; +}; + +const apiKey = process.env.SENDGRID_API_KEY; +if (!apiKey) { + throw new Error("SENDGRID_API_KEY is not set"); +} +sgMail.setApiKey(apiKey); +const courtMailer = createCourtMailer( + sgMail, + "https://v2.kleros.builders", + "https://https://v2.kleros.builders/settings/notifications" +); + +courtMailer + // .notifySignUp("jb@kleros.io", "https://v2.kleros.builders/onboarding") + .notifyAppealed("jb@kleros.io", "29", "March 25th at 3pm UTC") + // .notifyWinner("jb@kleros.io", "29", "1 ETH and 10,000 PNK", "Ruling") + // .notifyDrawnJuror("jb@kleros.io", "29") + .then(() => { + console.log("Email sent"); + }) + .catch((error) => { + console.error(error); + }); diff --git a/yarn.lock b/yarn.lock index 50a9cc5b7..89533a3a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5792,6 +5792,7 @@ __metadata: "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-solhint": "npm:^3.0.1" "@openzeppelin/contracts": "npm:^4.9.5" + "@sendgrid/mail": "npm:^8.1.1" "@typechain/ethers-v5": "npm:^11.1.2" "@typechain/hardhat": "npm:^7.0.0" "@types/chai": "npm:^4.3.11" @@ -8725,6 +8726,35 @@ __metadata: languageName: node linkType: hard +"@sendgrid/client@npm:^8.1.1": + version: 8.1.1 + resolution: "@sendgrid/client@npm:8.1.1" + dependencies: + "@sendgrid/helpers": "npm:^8.0.0" + axios: "npm:^1.6.4" + checksum: 59fe3e2d4d1ca935784586d0f76dbd61f01289b9e054b8ba867bb74cf00849d326b8402c5b81e694437fdef5db431f9743a118623e3c7d3ccba42b9c73d7e4c2 + languageName: node + linkType: hard + +"@sendgrid/helpers@npm:^8.0.0": + version: 8.0.0 + resolution: "@sendgrid/helpers@npm:8.0.0" + dependencies: + deepmerge: "npm:^4.2.2" + checksum: 23ff86dd7dcdcc6aec45a387978f9dcb31a7c2ec6f8450ca89542ecb63b6f7df7a2ce3ff6e5c9b003d9c6a7923fb74b00d8c58e70e6737ba0810360f0a074592 + languageName: node + linkType: hard + +"@sendgrid/mail@npm:^8.1.1": + version: 8.1.1 + resolution: "@sendgrid/mail@npm:8.1.1" + dependencies: + "@sendgrid/client": "npm:^8.1.1" + "@sendgrid/helpers": "npm:^8.0.0" + checksum: 8e77bba8ff7ae24ffc7afe2786c496dfc2936379f50e001a65e255294d0cf6b7810e1c1d27bb5aa2b69f79e8463f522908323f4b8f18c97fcdc4a7f20b9393c1 + languageName: node + linkType: hard + "@sentry-internal/feedback@npm:7.93.0": version: 7.93.0 resolution: "@sentry-internal/feedback@npm:7.93.0" @@ -13064,6 +13094,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.6.4": + version: 1.6.8 + resolution: "axios@npm:1.6.8" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 3f9a79eaf1d159544fca9576261ff867cbbff64ed30017848e4210e49f3b01e97cf416390150e6fdf6633f336cd43dc1151f890bbd09c3c01ad60bb0891eee63 + languageName: node + linkType: hard + "axobject-query@npm:^3.1.1": version: 3.2.1 resolution: "axobject-query@npm:3.2.1" @@ -19153,6 +19194,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.15.6": + version: 1.15.6 + resolution: "follow-redirects@npm:1.15.6" + peerDependenciesMeta: + debug: + optional: true + checksum: 70c7612c4cab18e546e36b991bbf8009a1a41cf85354afe04b113d1117569abf760269409cb3eb842d9f7b03d62826687086b081c566ea7b1e6613cf29030bf7 + languageName: node + linkType: hard + "for-each@npm:^0.3.3": version: 0.3.3 resolution: "for-each@npm:0.3.3"