<NME> send-email-card-drop-verification.ts
<BEF> import { Helpers } from 'graphile-worker';
import config from 'config';
import { inject } from '@cardstack/di';
import * as Sentry from '@sentry/node';

export default class SendEmailCardDropVerification {
  clock = inject('clock');
  email = inject('email');
  prismaManager = inject('prisma-manager', { as: 'prismaManager' });

  async perform(
    payload: {
      id: string;
      email: string;
    },
    helpers: Helpers
  ) {
    let prisma = await this.prismaManager.getClient();

    const request = (
      await prisma.emailCardDropRequest.findManyWithExpiry(
        {
          where: {
            id: payload.id,
          },
        },
        this.clock
      )
    )[0];

    if (!request) {
      let e = new Error(`Unable to find card drop request with id ${payload.id}`);
      Sentry.captureException(e, {
        tags: {
          event: 'send-email-card-drop-verification',
          alert: 'web-team',
        },
      });
      return;
    }

    if (request.isExpired) {
      let e = new Error(`Request with id ${payload.id} expired before sending verification link`);
      Sentry.captureException(e, {
        tags: {
          event: 'send-email-card-drop-verification',
          alert: 'web-team',
        },
      });
    const verificationLink = config.get('cardDrop.verificationUrl') + '?' + params.toString();

    const senderEmailAddress = config.get('aws.ses.supportEmail') as string;
    const expirationMinutes = config.get('cardDrop.email.expiryMinutes') as number;

    const emailTitle = 'Claim your Card Drop';
    const emailBodyHtml = `<h1></h1> This is your verification link: <a href="${verificationLink}">${verificationLink}</a> It will expire in ${expirationMinutes} minutes.`;
    const emailBodyText = `This is your verification link: ${verificationLink} It will expire in ${expirationMinutes} minutes.`;

    try {
      await this.email.send({
        to: payload.email,
        from: senderEmailAddress,
        text: emailBodyText,
        html: emailBodyHtml,
        title: emailTitle,
      });
    } catch (e) {
      helpers.logger.error('Failed to send verification email');
        templateName: 'card-drop-email',
        templateData: {
          verificationUrl: verificationLink,
        },
      });
    } catch (e) {
      helpers.logger.error('Failed to send verification email');
      helpers.logger.error((e as Error).toString());
      if ((e as Error).stack) helpers.logger.error((e as Error).stack!);

      Sentry.captureException(e, {
        tags: {
          event: 'send-email-card-drop-verification',
          alert: 'web-team',
        },
      });
      throw e;
    }
  }
}

declare module '@cardstack/hub/tasks' {
  interface KnownTasks {
    'send-email-card-drop-verification': SendEmailCardDropVerification;
  }
}

<MSG> hub: Change card drop email to use template (#3068)


<DFF> @@ -50,19 +50,15 @@ export default class SendEmailCardDropVerification {
     const verificationLink = config.get('cardDrop.verificationUrl') + '?' + params.toString();
 
     const senderEmailAddress = config.get('aws.ses.supportEmail') as string;
-    const expirationMinutes = config.get('cardDrop.email.expiryMinutes') as number;
-
-    const emailTitle = 'Claim your Card Drop';
-    const emailBodyHtml = `<h1></h1> This is your verification link: <a href="${verificationLink}">${verificationLink}</a> It will expire in ${expirationMinutes} minutes.`;
-    const emailBodyText = `This is your verification link: ${verificationLink} It will expire in ${expirationMinutes} minutes.`;
 
     try {
-      await this.email.send({
+      await this.email.sendTemplate({
         to: payload.email,
         from: senderEmailAddress,
-        text: emailBodyText,
-        html: emailBodyHtml,
-        title: emailTitle,
+        templateName: 'card-drop-email',
+        templateData: {
+          verificationUrl: verificationLink,
+        },
       });
     } catch (e) {
       helpers.logger.error('Failed to send verification email');

{'commit': 'diff --git a/packages/hub/node-tests/tasks/send-email-card-drop-verification-test.ts b/packages/hub/node-tests/tasks/send-email-card-drop-verification-test.ts\nindex 9f04dafc0..d210c5169 100644\n--- a/packages/hub/node-tests/tasks/send-email-card-drop-verification-test.ts\n+++ b/packages/hub/node-tests/tasks/send-email-card-drop-verification-test.ts\n@@ -43,7 +43,7 @@ class StubEmail {\n   static shouldThrow = false;\n   static lastSent: any = null;\n \n-  async send(email: any) {\n+  async sendTemplate(email: any) {\n     if (StubEmail.shouldThrow) throw new Error(\'Intentional error in stub email sending\');\n     StubEmail.lastSent = email;\n   }\n@@ -93,13 +93,8 @@ describe(\'SendEmailCardDropVerificationTask\', function () {\n \n     expect(StubEmail.lastSent.to).to.equal(\'anyone@test.test\');\n     expect(StubEmail.lastSent.from).to.equal(config.get(\'aws.ses.supportEmail\'));\n-    expect(StubEmail.lastSent.text).to.contain(link);\n-    expect(StubEmail.lastSent.html).to.contain(link);\n-\n-    let durationString = `${emailVerificationLinkExpiryMinutes} minutes`;\n-\n-    expect(StubEmail.lastSent.text).to.contain(durationString);\n-    expect(StubEmail.lastSent.html).to.contain(durationString);\n+    expect(StubEmail.lastSent.templateName).to.equal(\'card-drop-email\');\n+    expect(StubEmail.lastSent.templateData).to.deep.equal({ verificationUrl: link });\n   });\n \n   it(\'fails silently if the row does not exist\', async function () {\ndiff --git a/packages/hub/services/email.ts b/packages/hub/services/email.ts\nindex bad2cc535..9887a29f1 100644\n--- a/packages/hub/services/email.ts\n+++ b/packages/hub/services/email.ts\n@@ -39,6 +39,25 @@ export default class Email {\n       })\n     );\n   }\n+\n+  async sendTemplate(email: { to: string; from: string; templateName: string; templateData: Record<string, any> }) {\n+    return this.client.send(\n+      new SendEmailCommand({\n+        Destination: {\n+          CcAddresses: [],\n+          ToAddresses: [email.to],\n+        },\n+        Content: {\n+          Template: {\n+            TemplateName: email.templateName,\n+            TemplateData: JSON.stringify(email.templateData),\n+          },\n+        },\n+        FromEmailAddress: email.from,\n+        ReplyToAddresses: [email.from],\n+      })\n+    );\n+  }\n }\n \n declare module \'@cardstack/di\' {\ndiff --git a/packages/hub/tasks/send-email-card-drop-verification.ts b/packages/hub/tasks/send-email-card-drop-verification.ts\nindex adef9ada4..54d6f4261 100644\n--- a/packages/hub/tasks/send-email-card-drop-verification.ts\n+++ b/packages/hub/tasks/send-email-card-drop-verification.ts\n@@ -50,19 +50,15 @@ export default class SendEmailCardDropVerification {\n     const verificationLink = config.get(\'cardDrop.verificationUrl\') + \'?\' + params.toString();\n \n     const senderEmailAddress = config.get(\'aws.ses.supportEmail\') as string;\n-    const expirationMinutes = config.get(\'cardDrop.email.expiryMinutes\') as number;\n-\n-    const emailTitle = \'Claim your Card Drop\';\n-    const emailBodyHtml = `<h1></h1> This is your verification link: <a href="${verificationLink}">${verificationLink}</a> It will expire in ${expirationMinutes} minutes.`;\n-    const emailBodyText = `This is your verification link: ${verificationLink} It will expire in ${expirationMinutes} minutes.`;\n \n     try {\n-      await this.email.send({\n+      await this.email.sendTemplate({\n         to: payload.email,\n         from: senderEmailAddress,\n-        text: emailBodyText,\n-        html: emailBodyHtml,\n-        title: emailTitle,\n+        templateName: \'card-drop-email\',\n+        templateData: {\n+          verificationUrl: verificationLink,\n+        },\n       });\n     } catch (e) {\n       helpers.logger.error(\'Failed to send verification email\');\n', 'commit_subject': 'hub: Change card drop email to use template (#3068)', 'repo_name': 'cardstack/cardstack', 'committer': {'name': 'GitHub', 'email': '1505422b2465e9a84f6fdfaa161078890c593f06@github.com', 'time_sec': '1657301717', 'tz_offset': '-300', 'date': {'seconds': '1657301717'}}, 'commit_tree': '010707858b63bcfaf54d1856346145092df9ed4b', 'commit_message': 'hub: Change card drop email to use template (#3068)\n\n', 'language_name': 'JavaScript', 'license': 'mit', 'hunks': '@@ -50,19 +50,15 @@ export default class SendEmailCardDropVerification {\n     const verificationLink = config.get(\'cardDrop.verificationUrl\') + \'?\' + params.toString();\n \n     const senderEmailAddress = config.get(\'aws.ses.supportEmail\') as string;\n-    const expirationMinutes = config.get(\'cardDrop.email.expiryMinutes\') as number;\n-\n-    const emailTitle = \'Claim your Card Drop\';\n-    const emailBodyHtml = `<h1></h1> This is your verification link: <a href="${verificationLink}">${verificationLink}</a> It will expire in ${expirationMinutes} minutes.`;\n-    const emailBodyText = `This is your verification link: ${verificationLink} It will expire in ${expirationMinutes} minutes.`;\n \n     try {\n-      await this.email.send({\n+      await this.email.sendTemplate({\n         to: payload.email,\n         from: senderEmailAddress,\n-        text: emailBodyText,\n-        html: emailBodyHtml,\n-        title: emailTitle,\n+        templateName: \'card-drop-email\',\n+        templateData: {\n+          verificationUrl: verificationLink,\n+        },\n       });\n     } catch (e) {\n       helpers.logger.error(\'Failed to send verification email\');\n', 'addition_count': 5, 'deletion_count': 9, 'src_file': 'a/packages/hub/tasks/send-email-card-drop-verification.ts', 'tgt_file': 'b/packages/hub/tasks/send-email-card-drop-verification.ts', 'file_extension': '.ts', 'before_file': 'import { Helpers } from \'graphile-worker\';\nimport config from \'config\';\nimport { inject } from \'@cardstack/di\';\nimport * as Sentry from \'@sentry/node\';\n\nexport default class SendEmailCardDropVerification {\n  clock = inject(\'clock\');\n  email = inject(\'email\');\n  prismaManager = inject(\'prisma-manager\', { as: \'prismaManager\' });\n\n  async perform(\n    payload: {\n      id: string;\n      email: string;\n    },\n    helpers: Helpers\n  ) {\n    let prisma = await this.prismaManager.getClient();\n\n    const request = (\n      await prisma.emailCardDropRequest.findManyWithExpiry(\n        {\n          where: {\n            id: payload.id,\n          },\n        },\n        this.clock\n      )\n    )[0];\n\n    if (!request) {\n      let e = new Error(`Unable to find card drop request with id ${payload.id}`);\n      Sentry.captureException(e, {\n        tags: {\n          event: \'send-email-card-drop-verification\',\n          alert: \'web-team\',\n        },\n      });\n      return;\n    }\n\n    if (request.isExpired) {\n      let e = new Error(`Request with id ${payload.id} expired before sending verification link`);\n      Sentry.captureException(e, {\n        tags: {\n          event: \'send-email-card-drop-verification\',\n          alert: \'web-team\',\n        },\n      });\n    const verificationLink = config.get(\'cardDrop.verificationUrl\') + \'?\' + params.toString();\n\n    const senderEmailAddress = config.get(\'aws.ses.supportEmail\') as string;\n    const expirationMinutes = config.get(\'cardDrop.email.expiryMinutes\') as number;\n\n    const emailTitle = \'Claim your Card Drop\';\n    const emailBodyHtml = `<h1></h1> This is your verification link: <a href="${verificationLink}">${verificationLink}</a> It will expire in ${expirationMinutes} minutes.`;\n    const emailBodyText = `This is your verification link: ${verificationLink} It will expire in ${expirationMinutes} minutes.`;\n\n    try {\n      await this.email.send({\n        to: payload.email,\n        from: senderEmailAddress,\n        text: emailBodyText,\n        html: emailBodyHtml,\n        title: emailTitle,\n      });\n    } catch (e) {\n      helpers.logger.error(\'Failed to send verification email\');\n        templateName: \'card-drop-email\',\n        templateData: {\n          verificationUrl: verificationLink,\n        },\n      });\n    } catch (e) {\n      helpers.logger.error(\'Failed to send verification email\');\n      helpers.logger.error((e as Error).toString());\n      if ((e as Error).stack) helpers.logger.error((e as Error).stack!);\n\n      Sentry.captureException(e, {\n        tags: {\n          event: \'send-email-card-drop-verification\',\n          alert: \'web-team\',\n        },\n      });\n      throw e;\n    }\n  }\n}\n\ndeclare module \'@cardstack/hub/tasks\' {\n  interface KnownTasks {\n    \'send-email-card-drop-verification\': SendEmailCardDropVerification;\n  }\n}\n', 'message': 'hub: Change card drop email to use template (#3068)\n\n', 'author_name': 'GitHub', 'lang': 'ts'}