diff --git a/app/src/__tests__/send.test.ts b/app/src/__tests__/send.test.ts index 780502f..c502922 100644 --- a/app/src/__tests__/send.test.ts +++ b/app/src/__tests__/send.test.ts @@ -10,7 +10,7 @@ describe('Send email test', () => { googleUserEmail: process.env.GOOGLE_USER_EMAIL || '', googleClientId: process.env.GOOGLE_CLIENT_ID || '', googleClientSecret: process.env.GOOGLE_CLIENT_SECRET || '', - googleRereshToken: process.env.GOOGLE_REFRESH_TOKEN || '', + googleRefreshToken: process.env.GOOGLE_REFRESH_TOKEN || '', } await expect( diff --git a/app/src/lib/email/sender.ts b/app/src/lib/email/sender.ts index 14d6991..f25ae56 100644 --- a/app/src/lib/email/sender.ts +++ b/app/src/lib/email/sender.ts @@ -40,8 +40,14 @@ class EmailSender extends EmailTransport implements IEmailSender { throw new Error(EmailSchemaMessages.RECIPIENT_EMAIL_MAX) } + const from = transportOptions.auth?.user || process.env.GOOGLE_USER_EMAIL + + if (!from) { + throw new Error(TransportMessages.MISSING_SENDER_EMAIL) + } + return await this.transporter!.sendMail({ - from: transportOptions.auth?.user || process.env.GOOGLE_USER_EMAIL, + from, to: receivers, subject, ...(!isHtml && { text: content }), // Text email @@ -61,4 +67,8 @@ class EmailSender extends EmailTransport implements IEmailSender { } } +const TransportMessages: Record = { + MISSING_SENDER_EMAIL: 'Sender email address is not set. Ensure GOOGLE_USER_EMAIL is defined or pass it via oauth2 options.', +} + export default EmailSender diff --git a/app/src/lib/email/transport.ts b/app/src/lib/email/transport.ts index 25b7e46..48479a9 100644 --- a/app/src/lib/email/transport.ts +++ b/app/src/lib/email/transport.ts @@ -39,7 +39,7 @@ class EmailTransport implements IEmailTransport { googleUserEmail: options?.googleUserEmail || process.env.GOOGLE_USER_EMAIL, googleClientId: options?.googleClientId || process.env.GOOGLE_CLIENT_ID, googleClientSecret: options?.googleClientSecret || process.env.GOOGLE_CLIENT_SECRET, - googleRereshToken: options?.googleRereshToken || process.env.GOOGLE_REFRESH_TOKEN, + googleRefreshToken: options?.googleRefreshToken || process.env.GOOGLE_REFRESH_TOKEN, } this.#schema?.validate({ data: inputData }) @@ -53,7 +53,7 @@ class EmailTransport implements IEmailTransport { user: inputData.googleUserEmail, clientId: inputData.googleClientId, clientSecret: inputData.googleClientSecret, - refreshToken: inputData.googleRereshToken, + refreshToken: inputData.googleRefreshToken, }, }) } catch (err: unknown) { diff --git a/app/src/scripts/cli/lib/handleHtml.ts b/app/src/scripts/cli/lib/handleHtml.ts index 2f28d51..81720ca 100644 --- a/app/src/scripts/cli/lib/handleHtml.ts +++ b/app/src/scripts/cli/lib/handleHtml.ts @@ -10,42 +10,35 @@ export const handleSendHtmlEmail = async (options: EmailHtmlOptions) => { throw new Error('GOOGLE_USER_EMAIL .env variable is required') } - try { - const { - subject, - content: paragraphs = [], - recipients = [], - wysiwyg = null, - } = options - - if (paragraphs.length === 0 && typeof wysiwyg !== 'string') { - throw new Error('One of content or wysiwyg is required') - } - - // Clean data of whitespace - const emails = recipients.map(email => email.trim()) - - console.log(`Sending email to (${emails.length}) recipients`) - - const emailContent = await buildHtml({ - content: paragraphs, - recipients: emails, - sender: process.env.GOOGLE_USER_EMAIL, - wysiwyg, - }) - - await send({ - subject, - content: emailContent, - recipients: emails, - isHtml: true, - }) - } catch (err: unknown) { - if (err instanceof Error) { - console.log('[ERROR]: handle HTML email') - throw err - } + const { + subject, + content: paragraphs = [], + recipients = [], + wysiwyg = null, + } = options + + if (paragraphs.length === 0 && typeof wysiwyg !== 'string') { + throw new Error('One of content or wysiwyg is required') } + // Clean data of whitespace + const emails = recipients.map(email => email.trim()) + + console.log(`Sending email to (${emails.length}) recipients`) + + const emailContent = await buildHtml({ + content: paragraphs, + recipients: emails, + sender: process.env.GOOGLE_USER_EMAIL, + wysiwyg, + }) + + await send({ + subject, + content: emailContent, + recipients: emails, + isHtml: true, + }) + console.log('Process success') } diff --git a/app/src/types/email.schema.ts b/app/src/types/email.schema.ts index c358028..f7825b2 100644 --- a/app/src/types/email.schema.ts +++ b/app/src/types/email.schema.ts @@ -19,7 +19,7 @@ interface IOptionalParams { * Base schema for email validation within the `EmailTransport.sendEmail()` method * @property {string} [recipient] - (Optional) Email address of a recipient that will receive an email. Required if `recipients[]` is undefined. * @property {string[]} [recipients] - (Optional) Array of one or more email addresses of recipients that will receive an email. Required if `recipient` is undefined. - * @property {string} subject Email message title (max 100 characters) + * @property {string} subject Email message title (max 200 characters) * @property {string} content Email message content can be a simple text or HTML string (max 1500 characters) */ export const BaseEmailSchema = z.object({ @@ -57,7 +57,7 @@ export const EmailSchema = BaseEmailSchema.refine( * @typedef {object} EmailSenderType * @property {string} [recipient] - (Optional) Email address of a recipient that will receive an email. Required if `recipients[]` is undefined. * @property {string[]} [recipients] - (Optional) One (1) or more comma-separated email addresses of recipients that will receive an email. Required if `recipient` is undefined. - * @property {string} subject - Email message title (max 100 characters) + * @property {string} subject - Email message title (max 200 characters) * @property {string} content - Email message content can can be a simple text or HTML string (max 1500 characters) * @property {boolean} isHtml - Flag indicating if the `content` field is in HTML format. Defaults to `false`. */ diff --git a/app/src/types/schemavalidator.interface.ts b/app/src/types/schemavalidator.interface.ts index 6e64128..de8c518 100644 --- a/app/src/types/schemavalidator.interface.ts +++ b/app/src/types/schemavalidator.interface.ts @@ -61,9 +61,9 @@ export interface ISchemaValidator { /** * @description Get the base `ZodObject` schema from a `ZodEffects` schema * @param {ZodSchemaType} schema - The `ZodEffects` schema to get the `ZodObject` base schema from - * @returns {ZodObjectBasicType | null} The base schema or null if it's not a ZodEffects + * @returns {ZodObjectBasicType} The base schema */ - getBaseSchema (schema: ZodSchemaType): ZodObjectBasicType | null; + getBaseSchema (schema: ZodSchemaType): ZodObjectBasicType; /** * Retrieves only the base `ZodObject` subset from `this.schema` using `.pick()`. Finds the base `ZodObject` if `this.schema` is a `ZodEffects` schema. diff --git a/app/src/types/sender.interface.ts b/app/src/types/sender.interface.ts index f1461c7..713acd0 100644 --- a/app/src/types/sender.interface.ts +++ b/app/src/types/sender.interface.ts @@ -1,14 +1,15 @@ import type { EmailType } from '@/types/email.schema.js' +import type { SentMessageInfo } from '@/types/transport.types.js' import SchemaValidator from '@/lib/validator/schemavalidator.js' export interface IEmailSender { /** * Sends an email using Gmail SMTP and Google OAuth2 * @param {EmailType} params Input parameters for sending email - * @returns {Promise} Resolved Promise that sent the email + * @returns {Promise} Resolved Promise with Nodemailer send result * @throws {ZodIssue[]} Input parameter validation error/s */ - sendEmail (params: EmailType): Promise; + sendEmail (params: EmailType): Promise; /** * Retrieves the email sender's local zod schema wrapper diff --git a/app/src/types/transport.schema.ts b/app/src/types/transport.schema.ts index 53e4d52..ce48d62 100644 --- a/app/src/types/transport.schema.ts +++ b/app/src/types/transport.schema.ts @@ -8,7 +8,7 @@ export const TransportOath2Schema = z.object({ googleUserEmail: z.string().email().max(150), googleClientId: z.string().max(200), googleClientSecret: z.string().max(200), - googleRereshToken: z.string().max(500), + googleRefreshToken: z.string().max(500), }) export type TransportOath2SchemaType = z.infer;