이메일 자동 발송하기 with Cloud Functions

“AI 풀스택” 캠프의 5주차 과제는 이메일을 자동 발송하는 시스템 만들어 보기입니다.

과제의 주요 내용은 다음과 같습니다.

과제: “새로운 유저 등록 시, 환영 이메일을 자동 발송하기”

흐름은 아래와 같습니다. 참고로, 이번 실습은 GMAIL을 사용합니다.

1) 새로운 유저의 데이터가 DB에 생성된다.
2) 생성 시, 클라우드 함수가 트리거(on-create trigger)된다.
3) 유저가 등록한 이메일 주소로 환영 이메일을 발송한다.


이종화 파트너님이 제시해 주신 단계에 지금까지 진행했던 "풀스택”과 “AI 문과생도” 미션의 경험을 일부 가미하여 다음과 같이 진행 process를 다시 쭉 정리를 해 봤습니다.


  1. 클라이언트 쪽에서 아이디, 메일주소, 이름 정보를 firebase 서버로 전달한다.

  2. 클라이언트에서 전달하는 방식은 Post 방식을 적용한다.(이전에는 Get 방식을 사용했음)

  3. 전달 받은 정보를 Firestore Database에 “Users”라는 컬렉션에 새로운 문서로 저장한다.

  4. 새로운 문서가 저장될 때, 생성 일시에 대한 정보를 필드에 저장한다.

  5. 입력한 이메일 정보를 참고하여 “Welcome email”을 발송한다.

  6. “Welcome email” 형식은 별도의 html 문서에 있는 것을 활용한다.

  7. “Welcome email” 발송 여부에 대한 정보를 문서에 필드를 생성하여 저장한다.

  8. “Welcome email” 발송 할 때, 이름과 가입 일자에 대한 정보가 담긴 맞춤형 메일을 발송한다.


위에서 정리한 절차의 순서가 바뀌거나.. 몇 개의 절차를 묶어서 진행되기도 했다는 점을 감안하고 봐 주세요.



먼저, ChatGPT에게 구현하고 싶은 Cloud Function에 대한 의견을 물었습니다.

이에 대해 다음과 같은 답을 해 줍니다.

처음에서 생각했던 절차와 ChatGPT가 제시해 준 단계를 종합하여 이후 작업을 하나씩 진행하였습니다.


1. 클라이언트에서 입력한 정보를 Firebase 서버로 전달하기

위의 질문에 대한 답으로 정보를 입력 받는 부분을 javascript로 코드를 작성해서 주었는데요.

이왕이면 streamlit을 통해서 정보를 받으면 어떨까라는 생각을 해서, javascript 코드를 streamlit 용으로 바꿔 달라고 하니.. 결국 파이썬으로 코드를 짜 주었습니다.


import streamlit as st
import requests

# 폼 생성
st.title('User Data Submission')
with st.form("user_form"):
    user_id = st.text_input("ID")
    user_email = st.text_input("Email")
    user_name = st.text_input("Name")
    submitted = st.form_submit_button("Submit")

    if submitted:
        # 데이터를 Firebase Cloud Function으로 전송
        response = requests.post('https://your-cloud-function-url', json={
            'id': user_id,
            'email': user_email,
            'name': user_name
        })

        if response.ok:
            st.success("User data submitted successfully!")
        else:
            st.error("An error occurred.")


이 코드를 가지고 일단은 streamlit share가 아니라 그냥 로컬에서 실행하기로 하였습니다.


테스트를 해 보니, 위의 코드 중 “https://your-cloud-function-url” 부분만 firebase endpoint url로 변환하면 정상적으로 작동을 하였습니다.



2. 전달 받은 정보를 저장하는 Cloud Function 만들기


이 과정은 ChatGPT에게 다음과 같이 질문을 하여 답을 구하였습니다.


const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp();

exports.add_userddb = functions.https.onRequest(async (req, res) => {
    // POST 요청이 아닌 경우 에러 메시지 반환
    if (req.method !== 'POST') {
        res.status(403).send('Forbidden!');
        return;
    }

    try {
        const data = req.body;
        const user_id = data.id;
        const user_email = data.email;
        const user_name = data.name;

        // userDB 컬렉션에 문서 추가
        const writeResult = await admin.firestore().collection('userDB').add({
            id: user_id,
            email: user_email,
            name: user_name
        });

        // 성공 응답 전송
        res.json({result: `Document with ID: ${writeResult.id} added.`});
    } catch (error) {
        console.error('Error adding document: ', error);
        res.status(500).send('Internal Server Error');
    }
});


※ 여기서 index.js 파일에 모든 함수를 넣는 것이 아니라, 생성된 함수의 코드를 [함수명.js] 파일로 만들고 이를 index.js 파일에서 불러다가 사용하는 방식을 적용해 보았습니다.


3. 새로운 문서가 생성된 “일시” 정보를 필드에 저장하는 함수 만들기


이전에 만들었던 함수를 재활용하였습니다.

const functions = require('firebase-functions');
const admin = require('firebase-admin');

module.exports = functions.firestore
    .document('userDB/{userDBId}')
    .onCreate((snap, context) => {
        // 현재 시간스탬프 가져오기
        const currentTime = admin.firestore.FieldValue.serverTimestamp();

        // 'createdAt' 필드로 문서 업데이트
        return snap.ref.set({ createdAt: currentTime }, { merge: true });
    });


4. 생성된 정보를 활용하여 email을 발송하는 함수 만들기


ChatGPT에게 아래와 같이 질문을 하였고..

다음과 같은 답을 얻었습니다.


const functions = require('firebase-functions');
const admin = require('firebase-admin');
const nodemailer = require('nodemailer');

admin.initializeApp();

// Gmail을 사용하여 이메일을 보낼 SMTP 클라이언트 설정
const mailTransport = nodemailer.createTransport({
    service: 'gmail',
    auth: {
        user: '[email protected]', // 실제 Gmail 주소로 바꿔야 함
        pass: 'your-password' // 실제 Gmail 비밀번호 또는 앱 비밀번호로 바꿔야 함
    }
});

exports.sendWelcomeEmail = functions.firestore
    .document('userDB/{userDBId}')
    .onCreate(async (snap, context) => {
        const data = snap.data();

        if (data.email) {
            const email = data.email;

            const mailOptions = {
                from: '[email protected]', // 개인 이메일 주소가 반영됨. 
                to: email,
                subject: 'Welcome to Our Service!',
                html: '<p>Welcome! Thank you for joining us.</p>' // 실제 HTML 내용으로 변경 필요
            };

            try {
                await mailTransport.sendMail(mailOptions);

                console.log('Welcome email sent to:', email);

                // 이메일 발송 여부 체크 필드 업데이트
                return snap.ref.set({ emailSent: true }, { merge: true });
            } catch (error) {
                console.error('There was an error while sending the email:', error);
            }
        } else {
            console.log('No email provided, skipping sending email.');
        }
    });


이 작업을 진행하기 위해서는 먼저, Firebase 프로젝트에 Nodemailer 라이브러리가 설치되어야 합니다.

터미널에서 "npm install nodemailer" 실행. 


이메일 발송을 위해 gmail을 사용하는데, 계정의 비밀번호를 코드에 포함 시키는 것은 보안 상 안 좋기 때문에 Firebase의 환경 설정 기능과 Gmail의 앱 비밀번호를 사용합니다.

  • 먼저, gmail 앱 비밀번호 생성 방법

  • Firebase 환경 변수 설정 방법


(이 부분은 NPE님의 Firebase에서 가입 축하 이메일 자동으로 보내기 내용을 참고해서 gmail 주소와 앱 비밀번호 모두를 환경 변수로 설정하는 방식으로 변경하였습니다.)


5. 별도의 문서에 있는 html 메일 양식으로 이메일 보내기

위의 코드에서 아래 부분에 보내고 싶은 이메일의 html 양식을 넣으면 되는데요.


이 코드 자체에 html 코드를 넣는 것 보다 별도의 html 문서로 된 email 양식을 참조하는 방식을 적용해 보았고, 양식은 파트너님의 가이드 문서에 있는 것을 사용하였습니다.


이 email 양식에 가입 정보 중 “이름”과 오늘의 날짜 정보를 넣도록 작성된 최종 sendWelcomeEmail 함수는 다음과 같습니다.


// 날짜 포맷팅 함수
function formatDate(date) {
  const d = new Date(date);
  const year = d.getFullYear();
  const month = ('0' + (d.getMonth() + 1)).slice(-2);
  const day = ('0' + d.getDate()).slice(-2);
  return `${year}-${month}-${day}`;
}

// Welcome email 보내기 함수
async function sendWelcomeEmail(user) {
  const emailHtml = await readHtmlFile('./sample_welcome_email.html');
  // 사용자 이름과 현재 날짜로 placeholder 대체
  const personalizedHtml = emailHtml
    .replace('{{name}}', user.name)
    .replace('{{date}}', formatDate(new Date()));

  const mailOptions = {
    from: `Example App <[email protected]>`,
    to: user.email,
    subject: `Welcome to Example App!`,
    html: personalizedHtml,
  };

  await mailTransport.sendMail(mailOptions);
}


여기까지 작성된 코드를 배포함으로써 작업은 완료 되었고..

마지막으로.. 입력을 받는 streamlit 부분을 조금 업그레이드 해서 가입 정보를 계속 입력 받을 수 있도록 하였습니다. (streamlit share 적용)



다음은 이렇게 완성된 자동 이메일 발송 프로그램의 실행 결과입니다.



하다가 삼천포라 안 빠지고, 파트너님의 가이드 문서만 따라 간다면 그리 어렵지 않았을텐데요..

작업을 진행하면서 괜한 욕심을 부려.. 생각보다 시간도 오래 걸리고, 힘들었던 거 같습니다.

(곁가지들이 너무 많아서.. 정리도 힘드네요..)


그래도 이것 저것 해 보면서 많이 배웠다.. 애써 위안하며.. ㅎㅎ

“풀스택” 방의 5주차 미션을 이렇게 마무리하려고 합니다.


마지막으로..

청강임에도 불구하고.. 별도 단톡방 개설에.. 늦은 시간 후토크에.. 개인적인 질문에 꼼꼼하게 가이드까지 해 주신 이종화 파트너님께 감사와 고생하셨다는 말씀을 드립니다~^^


(일단 등록을 하고, 손을 좀 보도록 하겠습니다.)


#9기 풀스택

4
7개의 답글

(채용) 콘텐츠 마케터, AI 엔지니어, 백엔드 개발자

지피터스의 수 천개 AI 활용 사례 데이터를 AI로 재가공 할 인재를 찾습니다

👉 이 게시글도 읽어보세요