구글시트+앱스스크립트로 생일 축하 알림 자동화 만들기

🔎 소 개

매번 지인 생일을 캘린더에 적어두고 지나칠 때가 종종 있어서 자동으로 생일을 나에게 알려주고, 상대방에게 축하 메세지를 보내주는 도구가 있었으면 좋겠다는 생각에 생일을 자동으로 인식하여 생일자에게 이메일로 축하 이미지와 메세지를 발송하는 시스템을 만들게 되었습니다.

<주요 기능>

  • 생일자 정보를 구글 시트에 정리해두고

  • 매일 아침 9시에 오늘 생일인 사람을 찾아

  • 관리자에게 이메일로 미리 알려주고

  • 생일자에게도 생일 카드 이미지 & 축하 문구가 들어간 자동 축하 메일을 전송하는 시스템

<사용 도구>

  • Claude 3.7 Sonnet, ChatGPT : 주제 선정, 코드 작성, 오류 해결 방안 모색

  • 구글 스프레드 시트 : Birthday_List 시트, Message_Assets 시트 구성

  • 앱스 스크립트 : 구글 스프레드 시트에서 가져온 데이터로 웹 앱 만들기

🔎 진행 방법

<아이디어 구상 프롬프트>

넌 아주 유능한 구글앱 전문가야. 난 입문자로 구글 스프레드 시트의 정보를 활용하여 앱스스크립트로 자동화되는것을 공부하고 있어. 이름,생일의 리스트로 생일자가 있으면 나에게 먼저 알림 메일이 오게 하고 생일자에게 정해진 시간에 생일카드 이미지와 축하 문구가 이메일로 자동 전송되는 자동화를 만들고 싶어.

<스프레드시트 설정>

  • Birthday_List 시트

    A열: 이름

    B열: 관계

    C열: 생일 (YYYY-MM-DD 형식)

    D열: 이미지 선택

    E열: 문구 선택

    F열: 이메일

  • Message_Assets 시트

    A열: 이미지 번호

    B열: 이미지 링크

    C열: 문구 번호

    D열: 축하 문구 텍스트

시트데이터 입력 > 앱스 스크립트 코드 붙여넣고 저장 완료 >> 시간 기반 트리거 설정

function sendBirthdayReminder() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet();
  const birthdaySheet = sheet.getSheetByName("Birthday_List");
  const assetSheet = sheet.getSheetByName("Message_Assets");

  const today = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "MM-dd");
  const birthdayData = birthdaySheet.getDataRange().getValues();
  const assetData = assetSheet.getDataRange().getValues();

  const header = birthdayData[0];
  const nameIdx = header.indexOf("이름");
  const relationIdx = header.indexOf("관계");
  const dateIdx = header.indexOf("생일");
  const imageIdx = header.indexOf("이미지선택");
  const textIdx = header.indexOf("문구선택");
  const emailIdx = header.indexOf("이메일");

  // 이미지와 문구 데이터 처리 개선
  const imageMap = {};
  const textMap = {};
  
  for (let i = 1; i < assetData.length; i++) {
    if (assetData[i][0] && assetData[i][1]) {
      imageMap[assetData[i][0]] = assetData[i][1]; // 이미지번호 -> 이미지URL
    }
    if (assetData[i][2] && assetData[i][3]) {
      textMap[assetData[i][2]] = assetData[i][3];  // 문구번호 -> 문구텍스트
    }
  }
  
  // 디버깅용 로그
  Logger.log("이미지 데이터: " + JSON.stringify(imageMap));
  Logger.log("문구 데이터: " + JSON.stringify(textMap));

  let birthdayToday = [];

  for (let i = 1; i < birthdayData.length; i++) {
    const row = birthdayData[i];
    if (!row[nameIdx]) continue; // 빈 행 건너뛰기
    
    const name = row[nameIdx];
    const relation = row[relationIdx];
    let birthDate = row[dateIdx];
    
    // 날짜 형식 처리 개선
    let birthMonthDay;
    if (birthDate instanceof Date) {
      birthMonthDay = Utilities.formatDate(birthDate, Session.getScriptTimeZone(), "MM-dd");
    } else if (typeof birthDate === 'string') {
      // 문자열 날짜 처리 (예: "2025-03-31")
      const parts = birthDate.split('-');
      if (parts.length >= 3) {
        birthMonthDay = parts[1] + '-' + parts[2];
      }
    }
    
    Logger.log("비교: " + birthMonthDay + " vs 오늘: " + today);

    if (birthMonthDay === today) {
      const selectedImage = row[imageIdx];
      const selectedText = row[textIdx];
      const email = emailIdx >= 0 ? row[emailIdx] : null;
      
      // 이미지와 문구 가져오기
      const imageLink = imageMap[selectedImage] || "";
      const messageText = textMap[selectedText] || "";
      
      Logger.log("생일자: " + name + ", 이미지: " + imageLink + ", 문구: " + messageText);

      birthdayToday.push({
        name, relation, imageLink, messageText, email
      });
    }
  }

  if (birthdayToday.length === 0) {
    Logger.log("오늘 생일인 사람이 없습니다.");
    return;
  }

  // 관리자 알림 메일 내용
  let adminMessage = `
    <html>
    <head>
      <style>
        body { font-family: Arial, sans-serif; }
        .birthday-card { margin-bottom: 20px; border-bottom: 1px solid #ccc; padding-bottom: 20px; }
        .image-container { max-width: 100%; }
        img { max-width: 100%; height: auto; }
      </style>
    </head>
    <body>
      <h2>🎉 오늘 생일자 알림입니다!</h2>
  `;
  
  // 개인별 생일 메시지
  birthdayToday.forEach(person => {
    // 관리자 메일에 추가
    adminMessage += `
      <div class="birthday-card">
        <p><strong>👤 이름:</strong> ${person.name} (${person.relation})</p>
        <div class="image-container">
          <p><strong>🖼️ 이미지:</strong></p>
          <img src="${person.imageLink}" alt="생일 이미지" style="max-width: 400px;">
        </div>
        <p><strong>💌 문구:</strong> ${person.messageText}</p>
      </div>
    `;
    
    // 개인 생일 메일 전송
    // 개인 생일 메일 전송
if (person.email) {
  const birthdayMessage = `
    <html>
    <head>
      <style>
        body { font-family: 'Malgun Gothic', Arial, sans-serif; text-align: center; }
        .birthday-card { max-width: 600px; margin: 0 auto; padding: 20px; }
        img { max-width: 100%; height: auto; }
      </style>
    </head>
    <body>
      <div class="birthday-card">
        <h1>🎂 생일 축하합니다, ${person.name}님! 🎂</h1>
        <p>${person.messageText}</p>
        <p><strong>시밀레님이 보내신 생일카드입니다.</strong></p>
        <img src="${person.imageLink}" alt="생일 축하 이미지">
      </div>
    </body>
    </html>
  `;
      `;
      
      try {
        MailApp.sendEmail({
          to: person.email,
          subject: "🎂 생일 축하합니다!",
          htmlBody: birthdayMessage
        });
        Logger.log(person.name + "님에게 생일 이메일을 보냈습니다: " + person.email);
      } catch (e) {
        Logger.log("이메일 전송 오류 (" + person.email + "): " + e.message);
      }
    }
  });
  
  // 관리자 메일 마무리
  adminMessage += `
    </body>
    </html>
  `;

  // 관리자에게 요약 이메일 전송
  try {
    MailApp.sendEmail({
      to: Session.getActiveUser().getEmail(),
      subject: "🎂 오늘 생일자 알림",
      htmlBody: adminMessage
    });
    Logger.log("관리자에게 요약 이메일을 보냈습니다.");
  } catch (e) {
    Logger.log("관리자 이메일 전송 오류: " + e.message);
  }
}

<관리자에게 전송된 이메일 모습>

ImageBB Com 웹 사이트의 스크린 샷

<생일자에게 전송된 이메일 모습>

중간에 촛불이 달린 한국 생일 케이크

⏰ 결과와 배운 점

  1. 이미지 삽입에 제일 많은 시간을 투자한 것 같은데 이미지가 메일에 표시되지 않는 문제는 결국 해결하지 못했습니다.
    GPT나 Wrtn에서 만든 이미지 링크는 보통 일회성 / 비공개 링크라서, Gmail에서 이미지 삽입이 막히는 경우가 많더라고요.

    그래서 알게 된 점은 Imgur 등 공개 이미지 업로더를 써야 한다는 것

    이메일에 이미지를 넣을 땐 반드시 공개된 https://i.imgur.com/... 같은 주소여야 한다는 것을 배웠습니다.

  2. 바로 알림 메일을 확인하기 위해 생일자 날짜를 오늘로 변경하여 실행하느라 앱스 스크립트트에 시간 기반 트리거만 등록하고 코드 자체에는 시간 설정을 안했었더라구요.

    실행 확인했을때 저에게 알림메일이 오고 약 3초안에 생일자에게도 바로 알림 메일이 가는 것은 확인되어서 코드에 시간만 설정해서 추가하면 될 것 같습니다.

    <시간 기반 크리거 설정하기>

    자동으로 매일 아침 실행되게 하는 방법 👇

    1. 앱스 스크립트 상단 메뉴 → 시계 아이콘 ⏰ (트리거) 클릭

    2. 새 트리거 추가 버튼 (+) 클릭

    3. 함수 선택: sendBirthdayReminder

    4. 이벤트 소스: 시간 기반

    5. 트리거 유형: 일별 타이머

    6. 시간 선택: 예) 오전 8시~9시 사이

  3. 생일자 알림에서 제가 메세지를 바로 수정할 수 있는 흐름을 만들어보고 싶습니다.

스터디장님의 가르침과 용기를 복돋아주신 덕분에 걸음마 수준으로 직접 만들어 보면서 느낀점은 해결하지는 못했지만 많은 시간 고민하고 수정해나가며 오류를 발견해나가는 과정 자체도 하나의 성장이라는 생각이 들어 매우 뜻깊은 시간이었습니다. 😃
그리고 사례게시글 작성 서포터도 정말 유용하고 활용도 최고였습니다~☺️

👉 이 게시글도 읽어보세요