hunk.code_window:       transactionId: command.transactionId,
         phone,
         content,
         providerId: integration.providerId,
         payload: messagePayload,
         overrides,
         templateIdentifier: command.identifier,
         _jobId: command.jobId,
  
hunk.labels: keep keep keep replace keep keep keep keep
hunk.after_edit:       providerId: integration?.providerId,
  
hunk.file_path: apps/api/src/app/events/usecases/send-message/send-message-sms.usecase.ts
hunk.type: replace
file: import { Injectable } from '@nestjs/common';
  import {
    IEmailBlock,
    MessageRepository,
    NotificationStepEntity,
    NotificationRepository,
    SubscriberEntity,
    SubscriberRepository,
    OrganizationRepository,
    MessageEntity,
    NotificationEntity,
    OrganizationEntity,
    IntegrationEntity,
  } from '@novu/dal';
  import {
    ChannelTypeEnum,
    ExecutionDetailsSourceEnum,
    ExecutionDetailsStatusEnum,
    LogCodeEnum,
    StepTypeEnum,
  } from '@novu/shared';
  import * as Sentry from '@sentry/node';
  import { IAttachmentOptions, IEmailOptions } from '@novu/stateless';
  import { CreateLog } from '../../../logs/usecases/create-log/create-log.usecase';
  import { CompileTemplate } from '../../../content-templates/usecases/compile-template/compile-template.usecase';
  import { CompileTemplateCommand } from '../../../content-templates/usecases/compile-template/compile-template.command';
  import { MailFactory } from '../../services/mail-service/mail.factory';
  import { SendMessageCommand } from './send-message.command';
  import { SendMessageType } from './send-message-type.usecase';
  import {
    GetDecryptedIntegrations,
    GetDecryptedIntegrationsCommand,
  } from '../../../integrations/usecases/get-decrypted-integrations';
  import { CreateExecutionDetails } from '../../../execution-details/usecases/create-execution-details/create-execution-details.usecase';
  import {
    CreateExecutionDetailsCommand,
    DetailEnum,
  } from '../../../execution-details/usecases/create-execution-details/create-execution-details.command';
  
  @Injectable()
  export class SendMessageEmail extends SendMessageType {
    private mailFactory = new MailFactory();
  
    constructor(
      private subscriberRepository: SubscriberRepository,
      private notificationRepository: NotificationRepository,
      protected messageRepository: MessageRepository,
      protected createLogUsecase: CreateLog,
      protected createExecutionDetails: CreateExecutionDetails,
      private compileTemplate: CompileTemplate,
      private organizationRepository: OrganizationRepository,
      private getDecryptedIntegrationsUsecase: GetDecryptedIntegrations
    ) {
      super(messageRepository, createLogUsecase, createExecutionDetails);
    }
  
    public async execute(command: SendMessageCommand) {
      const emailChannel: NotificationStepEntity = command.step;
      const notification = await this.notificationRepository.findById(command.notificationId);
      const subscriber: SubscriberEntity = await this.subscriberRepository.findOne({
        _environmentId: command.environmentId,
        _id: command.subscriberId,
      });
      const organization: OrganizationEntity = await this.organizationRepository.findById(command.organizationId);
      const email = command.payload.email || subscriber.email;
  
      Sentry.addBreadcrumb({
        message: 'Sending Email',
      });
      const isEditorMode = !emailChannel.template.contentType || emailChannel.template.contentType === 'editor';
  
      const integration = (
        await this.getDecryptedIntegrationsUsecase.execute(
          GetDecryptedIntegrationsCommand.create({
            organizationId: command.organizationId,
            environmentId: command.environmentId,
            channelType: ChannelTypeEnum.EMAIL,
            findOne: true,
            active: true,
          })
        )
      )[0];
  
      const overrides = command.overrides[integration.providerId] || {};
      let subject = '';
      let content: string | IEmailBlock[] = '';
  
      try {
        subject = await this.renderContent(
          emailChannel.template.subject,
          emailChannel.template.subject,
          organization,
          subscriber,
          command
        );
  
        content = await this.getContent(isEditorMode, emailChannel, command, subscriber, subject, organization);
      } catch (e) {
        await this.createExecutionDetails.execute(
          CreateExecutionDetailsCommand.create({
            ...CreateExecutionDetailsCommand.getDetailsFromJob(command.job),
            detail: DetailEnum.MESSAGE_CONTENT_NOT_GENERATED,
            source: ExecutionDetailsSourceEnum.INTERNAL,
            status: ExecutionDetailsStatusEnum.FAILED,
            isTest: false,
            isRetry: false,
            raw: JSON.stringify({
              subject: emailChannel.template.subject,
              branding: {
                logo: organization.branding?.logo,
                color: organization.branding?.color || '#f47373',
              },
              blocks: [],
              step: {
                digest: !!command.events.length,
                events: command.events,
                total_count: command.events.length,
              },
              subscriber,
              ...command.payload,
            }),
          })
        );
      }
  
      const messagePayload = Object.assign({}, command.payload);
      delete messagePayload.attachments;
  
      const message: MessageEntity = await this.messageRepository.create({
        _notificationId: command.notificationId,
        _environmentId: command.environmentId,
        _organizationId: command.organizationId,
        _subscriberId: command.subscriberId,
        _templateId: notification._templateId,
        _messageTemplateId: emailChannel.template._id,
        content,
        subject,
        channel: ChannelTypeEnum.EMAIL,
        transactionId: command.transactionId,
        email,
        providerId: integration.providerId,
        payload: messagePayload,
        overrides,
        templateIdentifier: command.identifier,
        _jobId: command.jobId,
      });
  
      await this.createExecutionDetails.execute(
        CreateExecutionDetailsCommand.create({
          ...CreateExecutionDetailsCommand.getDetailsFromJob(command.job),
          detail: DetailEnum.MESSAGE_CREATED,
          source: ExecutionDetailsSourceEnum.INTERNAL,
          status: ExecutionDetailsStatusEnum.PENDING,
          messageId: message._id,
          isTest: false,
          isRetry: false,
          raw: JSON.stringify(messagePayload),
        })
      );
  
      const html = await this.compileTemplate.execute(
        CompileTemplateCommand.create({
          templateId: isEditorMode ? 'basic' : 'custom',
          customTemplate: emailChannel.template.contentType === 'customHtml' ? (content as string) : undefined,
          data: {
            subject,
            branding: {
              logo: organization.branding?.logo,
              color: organization.branding?.color || '#f47373',
            },
            blocks: isEditorMode ? content : [],
            step: {
              digest: !!command.events.length,
              events: command.events,
              total_count: command.events.length,
            },
            ...command.payload,
          },
        })
      );
  
      const attachments = (<IAttachmentOptions[]>command.payload.attachments)?.map(
        (attachment) =>
          <IAttachmentOptions>{
            file: attachment.file,
            mime: attachment.mime,
            name: attachment.name,
            channels: attachment.channels,
          }
      );
  
      const mailData: IEmailOptions = {
        to: email,
        subject,
        html,
        from: command.payload.$sender_email || integration?.credentials.from || 'no-reply@novu.co',
        attachments,
        id: message._id,
      };
  
      if (email && integration) {
        await this.sendMessage(integration, mailData, message, command, notification);
  
        return;
      }
      await this.sendErrors(email, integration, message, command, notification);
    }
  
    private async sendErrors(
      email,
      integration,
      message: MessageEntity,
      command: SendMessageCommand,
      notification: NotificationEntity
    ) {
      const errorMessage = 'Subscriber does not have an';
      const status = 'warning';
      const errorId = 'mail_unexpected_error';
  
      if (!email) {
        const mailErrorMessage = `${errorMessage} email address`;
  
        await this.createExecutionDetails.execute(
          CreateExecutionDetailsCommand.create({
            ...CreateExecutionDetailsCommand.getDetailsFromJob(command.job),
            messageId: message._id,
            detail: DetailEnum.SUBSCRIBER_NO_CHANNEL_DETAILS,
            source: ExecutionDetailsSourceEnum.INTERNAL,
            status: ExecutionDetailsStatusEnum.FAILED,
            isTest: false,
            isRetry: false,
          })
        );
  
        await this.sendErrorStatus(
          message,
          status,
          errorId,
          mailErrorMessage,
          command,
          notification,
          LogCodeEnum.SUBSCRIBER_MISSING_EMAIL
        );
      }
      if (!integration) {
        const integrationError = `${errorMessage} active email integration not found`;
  
        await this.createExecutionDetails.execute(
          CreateExecutionDetailsCommand.create({
            ...CreateExecutionDetailsCommand.getDetailsFromJob(command.job),
            messageId: message._id,
            detail: DetailEnum.SUBSCRIBER_NO_ACTIVE_INTEGRATION,
            source: ExecutionDetailsSourceEnum.INTERNAL,
            status: ExecutionDetailsStatusEnum.FAILED,
            isTest: false,
            isRetry: false,
          })
        );
  
        await this.sendErrorStatus(
          message,
          status,
          errorId,
          integrationError,
          command,
          notification,
          LogCodeEnum.MISSING_EMAIL_INTEGRATION
        );
      }
    }
  
    private async sendMessage(
      integration: IntegrationEntity,
      mailData: IEmailOptions,
      message: MessageEntity,
      command: SendMessageCommand,
      notification: NotificationEntity
    ) {
      const mailHandler = this.mailFactory.getHandler(integration, mailData.from);
  
      try {
        const result = await mailHandler.send(mailData);
  
        await this.createExecutionDetails.execute(
          CreateExecutionDetailsCommand.create({
            ...CreateExecutionDetailsCommand.getDetailsFromJob(command.job),
            messageId: message._id,
            detail: DetailEnum.MESSAGE_SENT,
            source: ExecutionDetailsSourceEnum.INTERNAL,
            status: ExecutionDetailsStatusEnum.SUCCESS,
            isTest: false,
            isRetry: false,
            raw: JSON.stringify(result),
          })
        );
  
        if (!result.id) {
          return;
        }
        await this.messageRepository.update(
          {
            _id: message._id,
          },
          {
            $set: {
              identifier: result.id,
            },
          }
        );
      } catch (error) {
        await this.createExecutionDetails.execute(
          CreateExecutionDetailsCommand.create({
            ...CreateExecutionDetailsCommand.getDetailsFromJob(command.job),
            messageId: message._id,
            detail: DetailEnum.PROVIDER_ERROR,
            source: ExecutionDetailsSourceEnum.INTERNAL,
            status: ExecutionDetailsStatusEnum.FAILED,
            isTest: false,
            isRetry: false,
            raw: JSON.stringify(error),
          })
        );
        await this.sendErrorStatus(
          message,
          'error',
          'mail_unexpected_error',
          'Error while sending email with provider',
          command,
          notification,
          LogCodeEnum.MAIL_PROVIDER_DELIVERY_ERROR,
          error
        );
      }
    }
  
    private async getContent(
      isEditorMode,
      emailChannel,
      command: SendMessageCommand,
      subscriber: SubscriberEntity,
      subject,
      organization: OrganizationEntity
    ): Promise<string | IEmailBlock[]> {
      if (isEditorMode) {
        const content: IEmailBlock[] = [...emailChannel.template.content] as IEmailBlock[];
        for (const block of content) {
          /*
           * We need to trim the content in order to avoid mail provider like GMail
           * to display the mail with `[Message clipped]` footer.
           */
          block.content = await this.renderContent(block.content, subject, organization, subscriber, command);
          block.content = block.content.trim();
          block.url = await this.renderContent(block.url || '', subject, organization, subscriber, command);
        }
  
        return content;
      }
  
      return emailChannel.template.content;
    }
  
    private async renderContent(
      content: string,
      subject,
      organization: OrganizationEntity,
      subscriber,
      command: SendMessageCommand
    ) {
      return await this.compileTemplate.execute(
        CompileTemplateCommand.create({
          templateId: 'custom',
          customTemplate: content as string,
          data: {
            subject,
            branding: {
              logo: organization.branding?.logo,
              color: organization.branding?.color || '#f47373',
            },
            blocks: [],
            step: {
              digest: !!command.events.length,
              events: command.events,
              total_count: command.events.length,
            },
            subscriber,
            ...command.payload,
          },
        })
      );
    }
  }
  