Skip to content

Commit

Permalink
Replace Sendgrid with Resend email service (#298)
Browse files Browse the repository at this point in the history
* replace sendgrid with resend

* correct comments

* refactor: move resend client to EmailService class

* feat: add debug logs when api key was not provided

* fix: fix email types according to resend lib

* feat: change email from value in email service

---------

Co-authored-by: Egor Skoropanov <[email protected]>
Co-authored-by: fruneen <[email protected]>
  • Loading branch information
3 people committed May 23, 2024
1 parent ce0f878 commit d6f45e3
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 100 deletions.
8 changes: 8 additions & 0 deletions template/apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ WEB_URL=http://localhost:3002
# openssl rand -base64 32
# ADMIN_KEY=

# Resend
# If you are using Resend for the development process,
# you need to use the following variables:
# https://resend.com/docs/introduction
# RESEND_API_KEY=...
# Link for emails testing:
# https://resend.com/docs/dashboard/emails/send-test-emails

# Cloud storage
# If you are using DO Spaces, then you can use the following guide to configure cloud storage environments:
# https://docs.digitalocean.com/products/spaces/reference/s3-sdk-examples/
Expand Down
2 changes: 1 addition & 1 deletion template/apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"@koa/multer": "3.0.2",
"@koa/router": "12.0.0",
"@paralect/node-mongo": "3.2.0",
"@sendgrid/mail": "7.7.0",
"@socket.io/redis-adapter": "8.1.0",
"@socket.io/redis-emitter": "5.1.0",
"app-constants": "workspace:*",
Expand All @@ -52,6 +51,7 @@
"node-schedule": "2.1.1",
"npm": "9.6.1",
"psl": "1.9.0",
"resend": "3.2.0",
"schemas": "workspace:*",
"socket.io": "4.6.1",
"winston": "3.8.2",
Expand Down
2 changes: 1 addition & 1 deletion template/apps/api/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const schema = z.object({
JWT_SECRET: z.string(),
REDIS_URI: z.string().optional(),
REDIS_ERRORS_POLICY: z.enum(['throw', 'log']).default('log'),
SENDGRID_API_KEY: z.string().optional(),
RESEND_API_KEY: z.string().optional(),
ADMIN_KEY: z.string().optional(),
MIXPANEL_API_KEY: z.string().optional(),
CLOUD_STORAGE_ENDPOINT: z.string().optional(),
Expand Down
52 changes: 15 additions & 37 deletions template/apps/api/src/services/email/email.service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import sendgrid from '@sendgrid/mail';
import { renderEmailHtml, Template } from 'mailer';
import { Resend } from 'resend';

import config from 'config';

import logger from 'logger';

import { EmailServiceConstructorProps, From, SendSendgridTemplateParams, SendTemplateParams } from './email.types';
import { EmailServiceConstructorProps, From, SendTemplateParams } from './email.types';

class EmailService {
resend?: Resend;

apiKey: string | undefined;

from: From;
Expand All @@ -16,63 +18,39 @@ class EmailService {
this.apiKey = apiKey;
this.from = from;

if (apiKey) sendgrid.setApiKey(apiKey);
if (apiKey) this.resend = new Resend(apiKey);
}

async sendTemplate<T extends Template>({ to, subject, template, params, attachments }: SendTemplateParams<T>) {
if (!this.apiKey) {
logger.error('[Sendgrid] API key is not provided');
if (!this.resend) {
logger.error('[Resend] API key is not provided');
logger.debug('[Resend] Email data:');
logger.debug({ subject, template, params });

return null;
}

const html = await renderEmailHtml({ template, params });

return sendgrid
return this.resend.emails
.send({
from: this.from,
from: `${this.from.name} <${this.from.email}>`,
to,
subject,
html,
attachments,
})
.then(() => {
logger.debug(`[Sendgrid] Sent email to ${to}.`);
logger.debug(`[Resend] Sent email to ${to}.`);
logger.debug({ subject, template, params });
});
}

async sendSendgridTemplate({
to,
subject,
templateId,
dynamicTemplateData,
attachments,
}: SendSendgridTemplateParams) {
if (!this.apiKey) {
logger.error('[Sendgrid] API key is not provided');
return null;
}

return sendgrid
.send({
from: this.from,
to,
subject,
templateId,
dynamicTemplateData,
attachments,
})
.then(() => {
logger.debug(`[Sendgrid] Sent email to ${to}.`);
logger.debug({ subject, templateId, dynamicTemplateData });
});
}
}

export default new EmailService({
apiKey: config.SENDGRID_API_KEY,
apiKey: config.RESEND_API_KEY,
from: {
email: 'notifications@ship.com',
email: 'no-reply@ship.paralect.com',
name: 'Ship',
},
});
24 changes: 11 additions & 13 deletions template/apps/api/src/services/email/email.types.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
import { Template, TemplateProps } from 'mailer';

export type From = { email: string; name: string };
export type From = {
email: string;
name: string;
};

export interface EmailServiceConstructorProps {
apiKey: string | undefined;
from: From;
}

interface Attachment {
content: string;
filename: string;
type?: string;
/** Content of an attached file. */
content?: string | Buffer;
/** Name of attached file. */
filename?: string | false | undefined;
/** Path where the attachment file is hosted */
path?: string;
}

export interface SendTemplateParams<T extends Template> {
to: string;
to: string | string[];
subject: string;
template: T;
params: TemplateProps[T];
attachments?: Attachment[];
}

export interface SendSendgridTemplateParams {
to: string;
subject: string;
templateId: string;
dynamicTemplateData: { [key: string]: unknown };
attachments?: Attachment[];
}
59 changes: 11 additions & 48 deletions template/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit d6f45e3

Please sign in to comment.