eletrotupi / tcc/ commit / ffd33a2

api: dispatch emails using resend

Pedro Lucas Porcellis porcellis@eletrotupi.com · 5 months ago ffd33a29c8ba15703f785399b5d0352f50c16d73 Browse files
Parents fe72d2d
Changed files
6
Additions
+113
Deletions
-2
api/package-lock.json modified+60 -1
api/package-lock.json
1 1
{
2 2
  "name": "orbit-api",
3 3
  "version": "0.0.0",
4 4
  "lockfileVersion": 3,
5 5
  "requires": true,
21 21
        "cors": "^2.8.6",
22 22
        "date-fns": "^4.1.0",
23 23
        "express": "^5.2.1",
24 24
        "jsonwebtoken": "^9.0.3",
25 25
        "morgan": "^1.10.1",
26 -
        "pg": "^8.20.0"
26 +
        "pg": "^8.20.0",
27 +
        "resend": "^6.12.3"
27 28
      },
28 29
      "devDependencies": {
29 30
        "@ava/typescript": "^6.0.0",
30 31
        "@types/better-sqlite3": "^7.6.13",
31 32
        "@types/express": "^5.0.6",
1124 1125
      },
1125 1126
      "funding": {
1126 1127
        "url": "https://github.com/sponsors/sindresorhus"
1127 1128
      }
1128 1129
    },
1130 +
    "node_modules/@stablelib/base64": {
1131 +
      "version": "1.0.1",
1132 +
      "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz",
1133 +
      "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==",
1134 +
      "license": "MIT"
1135 +
    },
1129 1136
    "node_modules/@standard-schema/spec": {
1130 1137
      "version": "1.1.0",
1131 1138
      "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
1132 1139
      "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
1133 1140
      "devOptional": true,
4183 4190
      "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
4184 4191
      "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
4185 4192
      "dev": true,
4186 4193
      "license": "MIT"
4187 4194
    },
4195 +
    "node_modules/fast-sha256": {
4196 +
      "version": "1.3.0",
4197 +
      "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz",
4198 +
      "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==",
4199 +
      "license": "Unlicense"
4200 +
    },
4188 4201
    "node_modules/fastq": {
4189 4202
      "version": "1.19.1",
4190 4203
      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
4191 4204
      "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
4192 4205
      "dev": true,
6374 6387
      },
6375 6388
      "funding": {
6376 6389
        "url": "https://github.com/sponsors/sindresorhus"
6377 6390
      }
6378 6391
    },
6392 +
    "node_modules/postal-mime": {
6393 +
      "version": "2.7.4",
6394 +
      "resolved": "https://registry.npmjs.org/postal-mime/-/postal-mime-2.7.4.tgz",
6395 +
      "integrity": "sha512-0WdnFQYUrPGGTFu1uOqD2s7omwua8xaeYGdO6rb88oD5yJ/4pPHDA4sdWqfD8wQVfCny563n/HQS7zTFft+f/g==",
6396 +
      "license": "MIT-0"
6397 +
    },
6379 6398
    "node_modules/postgres": {
6380 6399
      "version": "3.4.7",
6381 6400
      "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz",
6382 6401
      "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==",
6383 6402
      "devOptional": true,
6863 6882
      "license": "MIT",
6864 6883
      "engines": {
6865 6884
        "node": ">=0.10.0"
6866 6885
      }
6867 6886
    },
6887 +
    "node_modules/resend": {
6888 +
      "version": "6.12.3",
6889 +
      "resolved": "https://registry.npmjs.org/resend/-/resend-6.12.3.tgz",
6890 +
      "integrity": "sha512-FkEi6YPnVL96/LvH8+QP7NaeaBy5brYXwlRqUCqZZeNL0/iyKij18IPmyPXYauT/2ODn1JG04qKz+qlJfzqzTw==",
6891 +
      "license": "MIT",
6892 +
      "dependencies": {
6893 +
        "postal-mime": "2.7.4",
6894 +
        "svix": "1.92.2"
6895 +
      },
6896 +
      "engines": {
6897 +
        "node": ">=20"
6898 +
      },
6899 +
      "peerDependencies": {
6900 +
        "@react-email/render": "*"
6901 +
      },
6902 +
      "peerDependenciesMeta": {
6903 +
        "@react-email/render": {
6904 +
          "optional": true
6905 +
        }
6906 +
      }
6907 +
    },
6868 6908
    "node_modules/resolve-cwd": {
6869 6909
      "version": "3.0.0",
6870 6910
      "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
6871 6911
      "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
6872 6912
      "dev": true,
7339 7379
      "version": "2.1.0",
7340 7380
      "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
7341 7381
      "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
7342 7382
      "license": "MIT"
7343 7383
    },
7384 +
    "node_modules/standardwebhooks": {
7385 +
      "version": "1.0.0",
7386 +
      "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz",
7387 +
      "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==",
7388 +
      "license": "MIT",
7389 +
      "dependencies": {
7390 +
        "@stablelib/base64": "^1.0.0",
7391 +
        "fast-sha256": "^1.3.0"
7392 +
      }
7393 +
    },
7344 7394
    "node_modules/statuses": {
7345 7395
      "version": "2.0.2",
7346 7396
      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
7347 7397
      "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
7348 7398
      "license": "MIT",
7569 7619
      "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
7570 7620
      "dev": true,
7571 7621
      "license": "MIT",
7572 7622
      "engines": {
7573 7623
        "node": ">=0.8.0"
7624 +
      }
7625 +
    },
7626 +
    "node_modules/svix": {
7627 +
      "version": "1.92.2",
7628 +
      "resolved": "https://registry.npmjs.org/svix/-/svix-1.92.2.tgz",
7629 +
      "integrity": "sha512-ZmuA3UVvlnF9EgxlzmPtF7CKjQb64Z6OFlyfdDfU0sdcC7dJa+3aOYX5B9mA+RS6ch1AxBa4UP/l6KmqfGtWBQ==",
7630 +
      "license": "MIT",
7631 +
      "dependencies": {
7632 +
        "standardwebhooks": "1.0.0"
7574 7633
      }
7575 7634
    },
7576 7635
    "node_modules/tar": {
7577 7636
      "version": "7.5.2",
7578 7637
      "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz",
8385 8444
        "graphmatch": "^1.1.0"
8386 8445
      }
8387 8446
    }
8388 8447
  }
8389 8448
}
api/package.json modified+2 -1
api/package.json
1 1
{
2 2
  "name": "orbit-api",
3 3
  "version": "0.0.0",
4 4
  "description": "",
5 5
  "license": "ISC",
31 31
    "cors": "^2.8.6",
32 32
    "date-fns": "^4.1.0",
33 33
    "express": "^5.2.1",
34 34
    "jsonwebtoken": "^9.0.3",
35 35
    "morgan": "^1.10.1",
36 -
    "pg": "^8.20.0"
36 +
    "pg": "^8.20.0",
37 +
    "resend": "^6.12.3"
37 38
  },
38 39
  "devDependencies": {
39 40
    "@ava/typescript": "^6.0.0",
40 41
    "@types/better-sqlite3": "^7.6.13",
41 42
    "@types/express": "^5.0.6",
52 53
    "tsconfig-paths": "^4.2.0",
53 54
    "tsx": "^4.21.0",
54 55
    "typescript": "^6.0.2"
55 56
  }
56 57
}
/dev/null → api/src/lib/mail.ts
1 +
import { Resend } from 'resend';
2 +

3 +
// Initialized once when the module is first imported
4 +
export const resend = new Resend(process.env.RESEND_API_KEY);
5 +

6 +
// XXX: Bleh, resend doesn't expose their types so w/e
7 +
export async function sendEmail(opts: any): Promise<any> {
8 +
  return await resend.emails.send({
9 +
    from: process.env.RESEND_FROM ?? 'naoresponda@updates.cabotia.dev',
10 +
    ...opts,
11 +
  });
12 +
}
api/src/lib/queue/processors/mail.ts
1 1
import { Job } from 'bullmq';
2 2
import {
3 3
  MailJobName, MailJobData,
4 4
  WelcomeEmailPayload,
5 5
  PasswordResetPayload,
6 6
} from '@app/lib/queue/types';
7 7

8 +
import {
9 +
  sendWelcomeEmail
10 +
} from '@app/services/mail'
11 +

8 12
export async function mailProcessor(job: Job<MailJobData['data']>): Promise<void> {
9 13
  // job.name is the discriminant
10 14
  switch (job.name as MailJobName) {
11 15
    case MailJobName.WelcomeEmail: {
12 16
      const data = job.data as WelcomeEmailPayload;
13 17
      // await sendWelcomeEmail(data.userId);
14 18

15 19
      console.log("Dispatching an welcome email", data);
20 +

21 +
      sendWelcomeEmail(Number(data.userId));
22 +

16 23
      break;
17 24
    }
18 25

19 26
    case MailJobName.PasswordReset: {
20 27
      const data = job.data as PasswordResetPayload;
24 31

25 32
    default:
26 33
      throw new Error(`Unknown mail job: ${job.name}`);
27 34
  }
28 35
}
/dev/null → api/src/services/mail/index.ts
1 +
export { sendWelcomeEmail } from '@app/services/mail/welcome';
/dev/null → api/src/services/mail/welcome.ts
1 +
import { findUserById } from '@app/models/user';
2 +
import { sendEmail }  from '@app/lib/mail';
3 +

4 +
const email = (firstName: string) => {
5 +
  return (`
6 +
Oi! Seja bem vindo, ${firstName}.
7 +

8 +
Você criou sua conta com sucesso no orbit!
9 +

10 +
Qualquer coisa prende o grito!
11 +
  `)
12 +
}
13 +

14 +
export async function sendWelcomeEmail(userId: number): Promise<void> {
15 +
  const user = await findUserById(userId)!;
16 +

17 +
  console.log("Email/welcome: ", user);
18 +

19 +
  const { data, error } = await sendEmail({
20 +
    to: user!.email,
21 +
    subject: "Bem vindo ao orbit!",
22 +
    text: email(user!.firstName)
23 +
  });
24 +

25 +
  if (error) {
26 +
    console.error("Error sending email: ", error);
27 +
  }
28 +

29 +
  console.log("Email sent successfully!");
30 +
  console.log("Email ID:", data?.id);
31 +
}