우당탕탕 우리네 개발생활

[Nestjs] nodemailer로 메모리상의 엑셀파일을 전송해봤습니다 본문

tech

[Nestjs] nodemailer로 메모리상의 엑셀파일을 전송해봤습니다

미스터카멜레온 2024. 4. 4. 22:47

필요한 기능은 단순히 알맞은 데이터를 엑셀에 매핑한 후 해당 엑셀파일을 이메일로 전송하는 것이었습니다.

파일을 물리적으로 저장할 필요가 없었기에 메모리상에서만 파일 객체를 만들고 이를 이메일에 담아 전송할 수 있는 로직을 구현해야 했습니다.

 

nestjs 또는 nodejs 환경에서 이러한 직접적인 레퍼런스를 발견하지 못하였고 여러 가지 힌트를 얻어 직접 구현을 해봤습니다.

힌트는 다음과 같았습니다.

1. xlsx 라이브러리를 통해 만들어지는 xlsx객체는 다양한 형태(file, string, buffer, base64string 등..)의 객체로 변환이 가능

2. MIME타입의 이메일 바디를 raw하게 작성할 때 파일은 base64string으로 보통 변환하여 포함

3. nodemailer의 attachment필드는 file을 다양한 형식(uripath, base64string 등..)의 데이터로 담을 수 있는 방식들을 제공

 

결론적으로 파일 객체를 base64string으로 변환한 후 이를 이용한다면 물리적인 파일을 생성하지 않고도 기능을 구현할 수 있겠다고 생각했습니다. 아래 코드는 주요한 부분들 위주로 작성했습니다.

(xlsx 라이브러리의 사용법을 구체적으로 다루진 않을 것이기 때문에 궁금하신 분들은 해당 링크를 참고해 주시면 감사하겠습니다.)

 

// EmailService의 주요 로직

@Injectable()
class EmailService {
    private readonly transporter: nodemailer.Transporter;
    constructor(
        private readonly configService: ConfigService,
    ) {
        const transport = {
            SES: new AWS.SES({
                apiVersion: /**/,
                region: /**/,
                credentials: {
                    accessKeyId: this.configService.get('accessKeyId') as string,
                    secretAccessKey: this.configService.get('secretAccessKey') as string,
                },
            }),
        };
        this.transporter = nodemailer.createTransport(transport);
    }
	
    async function sendEmailWithFile(/**/): Promise<void> {
    	...
        const ws = XLSX.utils.aoa_to_sheet([
            [
                'name',
                'kind',
            ],
            ...animals.map((animal) => [
                animal.name,
                animal.kind,
            ]),
        ]);
        const workbook = XLSX.utils.book_new();
        XLSX.utils.book_append_sheet(workbook, ws, 'Data');

        // xlsx객체는 다양한 형식으로 변환이 가능합니다. 그 중 base64string으로 변환했습니다.
        const base64String = XLSX.write(workbook, { type: 'base64', bookType: 'xlsx' });
        const file: SentMessageInfoFile = {
            filename: /**/,
            content: base64String,
            encoding: 'base64',
        };
        
        await this.sendEmail(to, from, subject, html, file);
    }
    
    async function sendEmail(to, from, subject, html, file?): Promise<void> {
        // import { SentMessageInfo } from 'nodemailer';
        const sentMessageInfoArgument: SentMessageInfo = {
            to: /* 내용을 전송 받을 이메일 */,
            from: /* 전송 할 이메일 */,
            subject: /* 이메일 제목 */,
            html: /* email body에 포함될 html string */,
        };
        if (file) {
            // attachments의 형식 중 하나인 base64string을 다루고 싶을 때 사용하는 형태입니다.
            sentMessageInfoArgument.attachments = [
                {
                    filename: file.filename,
                    content: file.content,
                    encoding: file.encoding,
                },
            ];
        }

        await this.transporter.sendMail(sentMessageInfoArgument);
    }
}

 

 

마치며

nodemailer 라이브러리를 이용하여 파일을 첨부하여 이메일을 전송하는 기능 구현을 해봤습니다.

짧은 시간 내 학습하고 간편하게 적용할 수 있었습니다. aws-sdk에서 제공해 주는 ses관련 라이브러리에 있던 sendRawEmail(?) 함수를 이용하여 구현하지 못한 게 못내 아쉽지만 nodemailer의 오픈소스를 분석해 보면서 MIME타입의 이메일 body 작성을 어떤 식으로 하는지 확인해 보기로 결심했습니다. 훗날 공유할 만큼 유의미한 연구가 된다면 공유해 보도록 하겠습니다.

참조

https://medium.com/@boladebode/exploring-the-new-release-of-nest-js-version-10-and-the-migration-from-nest-modules-mailer-b80c574f89e6