메일 발신자 리스트에 자동으로 명절 인사 보내기

소개

시도하고자 했던 것과 그 이유를 알려주세요.

민족 대명절 설을 맞아ㅎㅎ 1년 간 함께 고생했던 감사한 분들에게 새해인사를 보내고 싶었습니다.

구글 앱스크립트와 GPT를 사용하여, 최근 1년 간 나의 메일발송이력이 있는 분들에게 자동으로 명절 인사가 전달되도록 했습니다.

진행 방법

어떤 도구를 사용했고, 어떻게 활용하셨나요?

Tip: 사용한 프롬프트 전문을 꼭 포함하고, 내용을 짧게 소개해 주세요.

Tip: 활용 이미지나 캡처 화면을 꼭 남겨주세요.

Tip: 코드 전문은 코드블록에 감싸서 작성해주세요. ( / 을 눌러 '코드 블록'을 선택)

구글 앱스크립트와 스프레드시트, GPT를 사용했어요.

-- 250210 베스트발표 ver. --

1) GPT에 코드 요청 > 앱스크립트에 입력

"앱스크립트에 입력할 코드를 짜줘. 지난 1년 간 지메일 발송 이력을 확인해서, 구글 스프레드시트에 <이름ㅣ메일주소ㅣ메일발송횟수>의 형식으로 출력되게 할 거야."

처음에 어떤 코드를 알려줬었는데 저장해두질 못했어요..ㅎㅎ

-다만, 위 코드로는

1. 이름이 중구난방으로 적히거나 'Unknown'으로 표시되고 / 2. 메일주소가 총 10개만 불러와지는 현상 발생. 아래와 같은 GPT의 조언(?)에 따라 코드 수정해 입력.

1. 이름이 중구난방으로 적히거나 'Unknown'으로 나옴

✅ 해결 방법

  • 이름이 없을 경우 이메일의 앞부분(@ 이전)을 이름으로 대체

  • Unknown을 없애기 위해 코드 수정

2. 메일주소가 총 10개만 불러와지는 현상

✅ 해결 방법

  • 검색 범위 늘리기
    GmailApp.search(query, 0, 2000);로 확장 (최대 2000개)

  • CC, BCC 수신자도 포함
    message.getCc()message.getBcc()도 포함하도록 수정

function getSentEmailContacts() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  sheet.clear(); // 기존 데이터 삭제
  
  // 헤더 설정
  sheet.appendRow(["이름", "메일주소", "메일발송횟수"]);

  var oneYearAgo = new Date();
  oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1); // 1년 전 날짜 계산
  var query = 'after:' + formatDate(oneYearAgo); // 1년 내 발송된 이메일 검색

  var emailCount = {}; // 이메일별 발송 횟수 저장
  var start = 0;
  var batchSize = 500; // Gmail API의 최대 검색 개수
  
  while (true) {
    var threads = GmailApp.search(query, start, batchSize);
    if (threads.length === 0) break; // 더 이상 가져올 데이터가 없으면 종료

    threads.forEach(thread => {
      var messages = thread.getMessages();
      messages.forEach(message => {
        if (message.getFrom().includes(Session.getActiveUser().getEmail())) { // 내가 보낸 메일인지 확인
          var recipients = [].concat(
            message.getTo().split(","), 
            message.getCc() ? message.getCc().split(",") : [],
            message.getBcc() ? message.getBcc().split(",") : []
          ); // TO + CC + BCC 포함

          recipients.forEach(recipient => {
            var emailMatch = recipient.match(/<(.+?)>/); 
            var email = emailMatch ? emailMatch[1] : recipient.trim();
            
            // 새로운 이름 추출 방식: <> 내부의 이메일을 제외한 부분을 가져옴
            var name = recipient.replace(/<.*?>/, "").trim(); 
            
            // 이름이 없거나 이메일이 그대로 나올 경우 보정
            if (!name || name === email) {
              name = email.split("@")[0]; // 이름이 없으면 이메일 앞부분 사용
            }

            if (!emailCount[email]) {
              emailCount[email] = { name: name, count: 0 };
            }
            emailCount[email].count += 1;
          });
        }
      });
    });

    start += batchSize; // 다음 500개 검색
  }

  // 데이터 시트에 저장
  Object.keys(emailCount).forEach(email => {
    sheet.appendRow([emailCount[email].name, email, emailCount[email].count]);
  });

  Logger.log("✅ 이메일 데이터 수집 완료!");
}

// 날짜 포맷 함수 (YYYY/MM/DD 형식)
function formatDate(date) {
  return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate();
}

2) 아래와 같이 입력되어 수정 필요

"이름 칼럼(A열)에 앞뒤로 큰따옴표가 붙어있을 경우 모두 삭제되도록 해줘. 그리고 11행의 jeon****@*****edata.com 이메일의 경우 내가 지메일에서 봤을 땐 '한**'이라는 이름이 확인되는데, 왜 이름 데이터가 제대로 불러와지지 않지? 이런 행이 많아서 이름의 호출 방식을 전체적으로 다시 한 번 체크해줘. 그리고 전체 데이터는 메일발송 횟수를 기준으로 내림차순 정렬되게 해줘."

function cleanAndSortEmailData() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var data = sheet.getDataRange().getValues();
  
  if (data.length < 2) {
    Logger.log("⚠️ 데이터가 충분하지 않습니다.");
    return;
  }

  // 1. 헤더를 제외한 데이터만 처리
  var headers = data[0];
  var emailData = data.slice(1);
  
  // 2. 큰따옴표 제거 및 이메일 기반 이름 보정
  var emailToName = {
    "[email protected]": "한정석",
    "[email protected]": "전준",
    "[email protected]": "원딕"
  };

  for (var i = 0; i < emailData.length; i++) {
    var name = emailData[i][0].trim();
    var email = emailData[i][1].trim();

    // 큰따옴표 제거
    emailData[i][0] = name.replace(/^"|"$/g, "");

    // 이메일 기반 이름 보정
    if (emailToName[email]) {
      emailData[i][0] = emailToName[email];
    }
  }

  // 3. 메일 발송 횟수(C열) 기준 내림차순 정렬
  emailData.sort(function(a, b) {
    return b[2] - a[2];
  });

  // 4. 기존 데이터 삭제 후 정리된 데이터 삽입
  sheet.clear();
  sheet.appendRow(headers);
  sheet.getRange(2, 1, emailData.length, emailData[0].length).setValues(emailData);
  
  Logger.log("✅ 데이터 정리 및 정렬 완료!");
}

3) 최종 다음과 같이 데이터 출력 완료

4) 메일 초안 요청

"오키. 이제 이 불러와진 이메일들에 메일이 나가도록 할 거야. 우선 '명절 연휴(최근에 한국은 민족 대명절 설이었단다..)는 끝났지만 힘내서 일하자!' 의 느낌으로 메일 초안을 작성해줘."

5) 메일 초안에 맞게 코드 수정 필요

"메일의 [이름] 부분의 경우, 이름 칼럼에서 한국어 이름을 확인가능한 경우에 한해 그 한글 이름만 포함되도록 할 수 있어?

예를 들어,

-이름 칼럼에 '홍해성'이라고 적혀있을 경우 > 안녕하세요, 홍해성님.

-이름 칼럼에 'Stella홍해성'이라고 적혀있을 경우 > 안녕하세요, 홍해성님.

-이름 칼럼에 'Stella'라고 적혀있을 경우 > 안녕하세요.

인삿말을 이렇게 적을 수 있도록 앱스크립트 코드를 짜줘."

function sendHolidayEmails() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var data = sheet.getDataRange().getValues();
  
  if (data.length < 2) {
    Logger.log("⚠️ 데이터가 충분하지 않습니다.");
    return;
  }

  var emailSubject = "💪 설 연휴가 끝났습니다! 힘내서 새 출발해요!";
  
  for (var i = 1; i < data.length; i++) {
    var name = data[i][0].trim(); // 이름 칼럼 (A열)
    var email = data[i][1].trim(); // 이메일 칼럼 (B열)

    if (!email) continue; // 이메일이 없으면 스킵

    // 1. 한글 이름만 추출
    var koreanName = extractKoreanName(name);

    // 2. 인사말 구성
    var greeting = koreanName ? `안녕하세요, ${koreanName}님.\n\n` : "안녕하세요.\n\n";

    // 3. 이메일 본문 작성
    var emailBody = greeting + 
      "설 연휴 잘 보내셨나요? 가족과 함께 따뜻한 시간을 보내셨길 바랍니다.\n\n" +
      "연휴가 끝나고 다시 바쁜 일상이 시작되었지만, 새로운 마음으로 힘차게 출발할 수 있길 바랍니다!\n\n" +
      "올해도 건강하시고, 계획하신 일들이 순조롭게 진행되길 기원합니다.\n" +
      "저희도 좋은 에너지를 담아 열심히 달려가겠습니다. 함께 멋진 한 해 만들어가요! 🚀\n\n" +
      "혹시 필요하신 것이 있거나 편하게 이야기 나누고 싶다면 언제든 연락 주세요.\n" +
      "좋은 하루 보내시고, 활기찬 한 주 시작하세요!\n\n" +
      "감사합니다.\n[보낸 사람 이름] 드림";

    // 4. 메일 발송
    GmailApp.sendEmail(email, emailSubject, emailBody);
    
    Logger.log("✅ 이메일 발송 완료: " + email);
  }
}

// 한글 이름만 추출하는 함수
function extractKoreanName(name) {
  var koreanMatch = name.match(/[가-힣]+/g); // 한글만 추출
  return koreanMatch ? koreanMatch.join("") : ""; // 여러 조각이 있으면 합쳐서 반환
}

6) 일부 동료들만 모은 시트2 생성 + 시트2에만 메일이 나갈 수 있는 코드 짜 달라

"위 코드 그대로, 시트2에만 메일이 나갈 수 있도록 코드를 생성해줘"

한국 텍스트가 강조된 Google 스프레드 시트의 스크린 샷
function sendHolidayEmails() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet1 = ss.getSheetByName("시트1"); // 데이터가 있는 시트
  var sheet2 = ss.getSheetByName("시트2"); // 허용된 이메일이 있는 시트

  if (!sheet1 || !sheet2) {
    Logger.log("⚠️ 시트1 또는 시트2를 찾을 수 없습니다.");
    return;
  }

  var data = sheet1.getDataRange().getValues();
  var allowedEmails = getAllowedEmails(sheet2); // 시트2에서 허용된 이메일 가져오기
  
  if (data.length < 2) {
    Logger.log("⚠️ 데이터가 충분하지 않습니다.");
    return;
  }

  var emailSubject = "💪 설 연휴가 끝났습니다! 힘내서 새 출발해요!";

  for (var i = 1; i < data.length; i++) {
    var name = data[i][0].trim(); // 이름 칼럼 (A열)
    var email = data[i][1].trim(); // 이메일 칼럼 (B열)

    if (!email || !allowedEmails.includes(email)) {
      Logger.log("❌ 발송 제외: " + email);
      continue; // 이메일이 없거나, 허용된 이메일 목록에 없으면 발송하지 않음
    }

    // 1. 한글 이름만 추출
    var koreanName = extractKoreanName(name);

    // 2. 인사말 구성
    var greeting = koreanName ? `안녕하세요, ${koreanName}님.\n\n` : "안녕하세요.\n\n";

    // 3. 이메일 본문 작성
    var emailBody = greeting + 
      "설 연휴 잘 보내셨나요? 가족과 함께 따뜻한 시간을 보내셨길 바랍니다.\n\n" +
      "연휴가 끝나고 다시 바쁜 일상이 시작되었지만, 새로운 마음으로 힘차게 출발할 수 있길 바랍니다!\n\n" +
      "올해도 건강하시고, 계획하신 일들이 순조롭게 진행되길 기원합니다.\n" +
      "저희도 좋은 에너지를 담아 열심히 달려가겠습니다. 함께 멋진 한 해 만들어가요! 🚀\n\n" +
      "혹시 필요하신 것이 있거나 편하게 이야기 나누고 싶다면 언제든 연락 주세요.\n" +
      "좋은 하루 보내시고, 활기찬 한 주 시작하세요!\n\n" +
      "감사합니다.\n[보낸 사람 이름] 드림";

    // 4. 메일 발송
    GmailApp.sendEmail(email, emailSubject, emailBody);
    
    Logger.log("✅ 이메일 발송 완료: " + email);
  }
}

// 📌 한글 이름만 추출하는 함수
function extractKoreanName(name) {
  var koreanMatch = name.match(/[가-힣]+/g); // 한글만 추출
  return koreanMatch ? koreanMatch.join("") : ""; // 여러 조각이 있으면 합쳐서 반환
}

// 📌 시트2에서 허용된 이메일 리스트 가져오는 함수
function getAllowedEmails(sheet) {
  var emailData = sheet.getDataRange().getValues();
  var emailList = [];

  for (var i = 1; i < emailData.length; i++) { // 헤더 제외
    var email = emailData[i][0].trim();
    if (email) {
      emailList.push(email);
    }
  }
  
  Logger.log("✅ 허용된 이메일 목록 로드 완료 (" + emailList.length + "개)");
  return emailList;
}

7) 실제 발송(이 되어버림...😂) 그러나 코딩은 잘 짜여진

8) 이모티콘 깨짐 현상 수정. 그런데 네이버 메일에서만 정상적으로 보임

"메일은 잘 발송됐어. 그런데 지메일에서는 여전히 이메일이 깨진 것처럼 보이는데, 메일을 수신한 네이버 메일에서는 이상없이 잘 보여. 왜 그런거야?"

왼쪽이 네이버(수신), 오른쪽이 지메일(발신)
function sendHolidayEmails() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet3 = ss.getSheetByName("시트3"); // ✅ 메일 발송이 허용된 시트3만 사용

  if (!sheet3) {
    Logger.log("⚠️ 시트3을 찾을 수 없습니다.");
    return;
  }

  var allowedEmails = getAllowedEmails(sheet3); // ✅ 시트3에서 허용된 이메일 가져오기

  if (allowedEmails.length === 0) {
    Logger.log("⚠️ 시트3에 이메일 데이터가 없습니다.");
    return;
  }

  var emailSubject = "설 연휴가 끝났습니다! 힘내서 새 출발해요!"; // ✅ 이모티콘 제거 (Gmail 인코딩 문제 해결)

  for (var i = 0; i < allowedEmails.length; i++) {
    var email = allowedEmails[i];

    // 1. 한글 이름만 추출
    var koreanName = extractKoreanName(email);

    // 2. 인사말 구성
    var greeting = koreanName ? `안녕하세요, <b>${koreanName}님</b>.<br><br>` : "안녕하세요.<br><br>";

    // 3. 이메일 본문 작성 (HTML 포맷 적용, Unicode HTML 엔티티 사용)
    var emailBody = greeting + 
      "&#x1F389; 설 연휴 잘 보내셨나요? 가족과 함께 따뜻한 시간을 보내셨길 바랍니다.<br><br>" +
      "연휴가 끝나고 다시 바쁜 일상이 시작되었지만, 새로운 마음으로 힘차게 출발할 수 있길 바랍니다!<br><br>" +
      "올해도 건강하시고, 계획하신 일들이 순조롭게 진행되길 기원합니다.<br>" +
      "저희도 좋은 에너지를 담아 열심히 달려가겠습니다. 함께 멋진 한 해 만들어가요! &#x1F680;<br><br>" +
      "&#x2728; 혹시 필요하신 것이 있거나 편하게 이야기 나누고 싶다면 언제든 연락 주세요.<br>" +
      "&#x1F4AA; 좋은 하루 보내시고, 활기찬 한 주 시작하세요!<br><br>" +
      "감사합니다.<br><b>홍해성 드림</b>"; // ✅ 보낸 사람 이름 고정

    // 4. HTML 형식으로 이메일 발송
    GmailApp.sendEmail(email, emailSubject, "", { htmlBody: emailBody });

    Logger.log("✅ 이메일 발송 완료: " + email);
  }
}

// 📌 한글 이름만 추출하는 함수
function extractKoreanName(email) {
  var nameMatch = email.match(/[가-힣]+/g); // 한글만 추출
  return nameMatch ? nameMatch.join("") : ""; // 여러 조각이 있으면 합쳐서 반환
}

// 📌 시트3의 B열에서 허용된 이메일 리스트 가져오는 함수
function getAllowedEmails(sheet) {
  var emailData = sheet.getDataRange().getValues();
  var emailList = [];

  for (var i = 1; i < emailData.length; i++) { // 헤더 제외
    var email = emailData[i][1].trim(); // ✅ B열(두 번째 열)에서 이메일 가져오기
    if (email) {
      emailList.push(email);
    }
  }

  Logger.log("✅ 허용된 이메일 목록 로드 완료 (" + emailList.length + "개)");
  return emailList;
}

8) 찐_최종

한국의 메시지 스크린 샷

결과와 배운 점

배운 점과 나만의 꿀팁을 알려주세요.

  1. 앱스크립트 오류가 잦다. F5 자주 눌러야 했음.

  2. 확실히 구글 앱스크립트를 통해 > 구글 지메일로 업무를 연동하니 흐름이 매끄럽다. 내 요청사항을 잘 이해한다는 게 느껴지고, 시스템도 잘 구축되어 있는 느낌.
    ex) 코드에 새로운 TASK를 추가하면 해당 작업에 대한 권한 요청이 온다. (메일 전송을 허용하시겠습니까 팝업창 등등)

  3. 광밤님 tip: GPT가 내 말을 잘 이해하지 못하면 구체적인 예시를 줘라. 실제로 코드 수정 속도 훨씬 2배 이상 빨라짐!

-- 250206 스터디 발표 ver. --

Google Apps 스크립트
function getSentEmailContacts() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  sheet.clear(); // 기존 데이터 삭제
  
  // 헤더 설정
  sheet.appendRow(["이름", "메일주소", "메일발송횟수"]);
  
  var oneYearAgo = new Date();
  oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1); // 1년 전 날짜 계산
  
  var query = 'after:' + formatDate(oneYearAgo); // 1년 내 발송된 이메일 검색
  var threads = GmailApp.search(query, 0, 500); // 최대 500개 스레드 검색
  var emailCount = {}; // 이메일별 발송 횟수 저장
  
  threads.forEach(thread => {
    var messages = thread.getMessages();
    messages.forEach(message => {
      if (message.getFrom().includes(Session.getActiveUser().getEmail())) { // 내가 보낸 메일인지 확인
        var recipients = message.getTo().split(","); // 여러 수신자 처리
        recipients.forEach(recipient => {
          var emailMatch = recipient.match(/<(.+?)>/); 
          var email = emailMatch ? emailMatch[1] : recipient.trim();
          var nameMatch = recipient.match(/"(.+?)"/);
          var name = nameMatch ? nameMatch[1] : "Unknown";

          if (!emailCount[email]) {
            emailCount[email] = { name: name, count: 0 };
          }
          emailCount[email].count += 1;
        });
      }
    });
  });

  // 데이터 시트에 저장
  Object.keys(emailCount).forEach(email => {
    sheet.appendRow([emailCount[email].name, email, emailCount[email].count]);
  });

  Logger.log("✅ 이메일 데이터 수집 완료!");
}

// 날짜 포맷 함수 (YYYY/MM/DD 형식)
function formatDate(date) {
  return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate();
}
Google 애드워즈

다만 일부 수정이 필요했어요.

  1. 노란 표시한 것처럼 Unknown이라고 뜨거나, 메일주소가 그대로 '이름' 칼럼에 입력됨.

  2. 내가 1년 동안 이 분들한테만 메일을 보냈다고?

한국 및 영어 텍스트가있는 스프레드 시트의 스크린 샷

위에 대한 GPT의 대답

  1. 이름 관련

    1️⃣ 이름 없이 이메일 주소만 존재하는 경우

    • 어떤 이메일은 "홍길동 <[email protected]>" 형태로 이름이 포함됨

    • 하지만 "[email protected]" 같은 순수 이메일 주소만 있을 경우, nameMatch에서 이름을 찾지 못해서 Unknown으로 표시됨

    2️⃣ 데이터 추출 과정에서 이름을 제대로 인식하지 못한 경우

    해결 방법

    • 이름이 없을 경우 이메일의 앞부분(@ 이전)을 이름으로 대체

    • Unknown을 없애기 위해 코드 수정

  2. 데이터 수 관련

    🚨 원인 분석

    • GmailApp.search(query, 0, 500); → 최대 500개의 메일 스레드만 검색

    • Gmail에서는 1개의 스레드에 여러 개의 메일이 포함될 수 있음, 하지만 일부 스레드가 검색되지 않을 수도 있음

    • message.getTo()만 확인하고 있지만, CC, BCC에 포함된 사람들은 집계되지 않음

    ✅ 해결 방법

    1️⃣ 검색 범위 늘리기
    GmailApp.search(query, 0, 2000);로 확장 (최대 2000개)

    2️⃣ CC, BCC 수신자도 포함
    message.getCc()message.getBcc()도 포함하도록 수정

function getSentEmailContacts() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  sheet.clear(); // 기존 데이터 삭제
  
  // 헤더 설정
  sheet.appendRow(["이름", "메일주소", "메일발송횟수"]);
  
  var oneYearAgo = new Date();
  oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1); // 1년 전 날짜 계산
  
  var query = 'after:' + formatDate(oneYearAgo); // 1년 내 발송된 이메일 검색
  var threads = GmailApp.search(query, 0, 1000); // 더 많은 이메일 검색
  var emailCount = {}; // 이메일별 발송 횟수 저장
  
  threads.forEach(thread => {
    var messages = thread.getMessages();
    messages.forEach(message => {
      if (message.getFrom().includes(Session.getActiveUser().getEmail())) { // 내가 보낸 메일인지 확인
        var recipients = message.getTo().split(","); // 여러 수신자 처리
        recipients.forEach(recipient => {
          var emailMatch = recipient.match(/<(.+?)>/); 
          var email = emailMatch ? emailMatch[1] : recipient.trim();
          var nameMatch = recipient.match(/"(.+?)"/);
          var name = nameMatch ? nameMatch[1] : email.split("@")[0]; // Unknown 대신 이메일 앞부분 사용

          if (!emailCount[email]) {
            emailCount[email] = { name: name, count: 0 };
          }
          emailCount[email].count += 1;
        });
      }
    });
  });

  // 데이터 시트에 저장
  Object.keys(emailCount).forEach(email => {
    sheet.appendRow([emailCount[email].name, email, emailCount[email].count]);
  });

  Logger.log("✅ 이메일 데이터 수집 완료!");
}

// 날짜 포맷 함수 (YYYY/MM/DD 형식)
function formatDate(date) {
  return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate();
}

검색 범위 재조정 - 페이지네이션(반복호출)

function getSentEmailContacts() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  sheet.clear(); // 기존 데이터 삭제
  
  // 헤더 설정
  sheet.appendRow(["이름", "메일주소", "메일발송횟수"]);

  var oneYearAgo = new Date();
  oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1); // 1년 전 날짜 계산
  var query = 'after:' + formatDate(oneYearAgo); // 1년 내 발송된 이메일 검색

  var emailCount = {}; // 이메일별 발송 횟수 저장
  var start = 0;
  var batchSize = 500; // Gmail API의 최대 검색 개수
  
  while (true) {
    var threads = GmailApp.search(query, start, batchSize);
    if (threads.length === 0) break; // 더 이상 가져올 데이터가 없으면 종료

    threads.forEach(thread => {
      var messages = thread.getMessages();
      messages.forEach(message => {
        if (message.getFrom().includes(Session.getActiveUser().getEmail())) { // 내가 보낸 메일인지 확인
          var recipients = [].concat(
            message.getTo().split(","), 
            message.getCc() ? message.getCc().split(",") : [],
            message.getBcc() ? message.getBcc().split(",") : []
          ); // TO + CC + BCC 포함

          recipients.forEach(recipient => {
            var emailMatch = recipient.match(/<(.+?)>/); 
            var email = emailMatch ? emailMatch[1] : recipient.trim();
            var nameMatch = recipient.match(/"(.+?)"/);
            var name = nameMatch ? nameMatch[1] : email.split("@")[0]; // Unknown 대신 이메일 앞부분 사용

            if (!emailCount[email]) {
              emailCount[email] = { name: name, count: 0 };
            }
            emailCount[email].count += 1;
          });
        }
      });
    });

    start += batchSize; // 다음 500개 검색
  }

  // 데이터 시트에 저장
  Object.keys(emailCount).forEach(email => {
    sheet.appendRow([emailCount[email].name, email, emailCount[email].count]);
  });

  Logger.log("✅ 이메일 데이터 수집 완료!");
}

// 날짜 포맷 함수 (YYYY/MM/DD 형식)
function formatDate(date) {
  return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate();
}

이름에 여전히 오류: 이름 설정이 되어있는데도 이메일의 아이디만 불러와짐

GPT 대답

🔍 문제 분석

  • 이메일이 "김*희/산학협력팀(**) <wngm****@***.ac.kr>" 형식인데도 "wng****"로 표시됨

  • 이는 기존 코드에서 "이름"을 추출할 때 " "(큰따옴표) 안의 값만 가져오도록 했기 때문

  • 일부 이메일에는 큰따옴표 없이 이름이 포함될 수 있음 → 이를 제대로 가져와야 함

  • 따라서 이름이 정상적으로 존재하면 그 값을 가져오고, 없다면 이메일 앞부분을 대체하도록 수정

한국 문자 메시지의 스크린 샷

✅ 해결 방법

📌 이름을 가져오는 방법 개선

  • 기존에는 recipient.match(/"(.+?)"/);를 사용해서 큰따옴표 안의 값을 추출했는데,
    큰따옴표 없이도 이름을 가져올 수 있도록 개선 필요

  • <> 내부의 이메일을 제외하고, 남은 문자열을 이름으로 인식하는 방식으로 수정

function getSentEmailContacts() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  sheet.clear(); // 기존 데이터 삭제
  
  // 헤더 설정
  sheet.appendRow(["이름", "메일주소", "메일발송횟수"]);

  var oneYearAgo = new Date();
  oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1); // 1년 전 날짜 계산
  var query = 'after:' + formatDate(oneYearAgo); // 1년 내 발송된 이메일 검색

  var emailCount = {}; // 이메일별 발송 횟수 저장
  var start = 0;
  var batchSize = 500; // Gmail API의 최대 검색 개수
  
  while (true) {
    var threads = GmailApp.search(query, start, batchSize);
    if (threads.length === 0) break; // 더 이상 가져올 데이터가 없으면 종료

    threads.forEach(thread => {
      var messages = thread.getMessages();
      messages.forEach(message => {
        if (message.getFrom().includes(Session.getActiveUser().getEmail())) { // 내가 보낸 메일인지 확인
          var recipients = [].concat(
            message.getTo().split(","), 
            message.getCc() ? message.getCc().split(",") : [],
            message.getBcc() ? message.getBcc().split(",") : []
          ); // TO + CC + BCC 포함

          recipients.forEach(recipient => {
            var emailMatch = recipient.match(/<(.+?)>/); 
            var email = emailMatch ? emailMatch[1] : recipient.trim();
            
            // 새로운 이름 추출 방식: <> 내부의 이메일을 제외한 부분을 가져옴
            var name = recipient.replace(/<.*?>/, "").trim(); 
            
            // 이름이 없거나 이메일이 그대로 나올 경우 보정
            if (!name || name === email) {
              name = email.split("@")[0]; // 이름이 없으면 이메일 앞부분 사용
            }

            if (!emailCount[email]) {
              emailCount[email] = { name: name, count: 0 };
            }
            emailCount[email].count += 1;
          });
        }
      });
    });

    start += batchSize; // 다음 500개 검색
  }

  // 데이터 시트에 저장
  Object.keys(emailCount).forEach(email => {
    sheet.appendRow([emailCount[email].name, email, emailCount[email].count]);
  });

  Logger.log("✅ 이메일 데이터 수집 완료!");
}

// 날짜 포맷 함수 (YYYY/MM/DD 형식)
function formatDate(date) {
  return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate();
}

여기까지 작업한 결과

한국어 텍스트가 장착 된 Google 스프레드 시트

이제 이들에게 메일을 보내는 기능을 추가해달라고 요청했어요.

다만 실제로 메일이 나가면 안 되기 때문에, 다른 사람들에게는 테스트로, 제 메일에만 실제로 메일이 발송되도록 요청했어요.

한국어 문자 메시지의 스크린 샷
한국어로 된 문자 메시지
function getSentEmailContactsAndSendTestEmails() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("이메일 목록");
  if (!sheet) {
    sheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("이메일 목록");
  }
  sheet.clear();
  sheet.appendRow(["이름", "메일주소", "메일발송횟수"]);

  var logSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("작업 로그");
  if (!logSheet) {
    logSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("작업 로그");
  }
  logSheet.clear();
  logSheet.appendRow(["시간", "진행 상황"]);

  function logProgress(message) {
    logSheet.appendRow([new Date().toLocaleString(), message]);
  }

  var isTestMode = true; // 기본적으로 모든 메일은 발송 안 함
  var testEmail = "[email protected]"; // ✅ 이 이메일에게만 실제 발송

  logProgress("✅ 스크립트 실행 시작");

  var oneYearAgo = new Date();
  oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
  var query = 'after:' + formatDate(oneYearAgo);

  var emailCount = {};
  var start = 0;
  var batchSize = 500;

  while (true) {
    var threads = GmailApp.search(query, start, batchSize);
    if (threads.length === 0) break;

    logProgress(`🔍 ${threads.length}개의 이메일 스레드 검색됨`);

    threads.forEach(thread => {
      var messages = thread.getMessages();
      messages.forEach(message => {
        if (message.getFrom().includes(Session.getActiveUser().getEmail())) {
          var recipients = [].concat(
            message.getTo().split(","), 
            message.getCc() ? message.getCc().split(",") : [],
            message.getBcc() ? message.getBcc().split(",") : []
          );

          recipients.forEach(recipient => {
            var emailMatch = recipient.match(/<(.+?)>/); 
            var email = emailMatch ? emailMatch[1] : recipient.trim();
            var name = recipient.replace(/<.*?>/, "").trim();

            if (!name || name === email) {
              name = email.split("@")[0]; 
            }

            if (!emailCount[email]) {
              emailCount[email] = { name: name, count: 0 };
            }
            emailCount[email].count += 1;
          });
        }
      });
    });

    start += batchSize;
  }

  logProgress("✅ 데이터 처리 완료, 이메일 목록 스프레드시트에 저장 시작");

  Object.keys(emailCount).forEach(email => {
    sheet.appendRow([emailCount[email].name, email, emailCount[email].count]);

    // ✉️ 이메일 제목 및 본문 업데이트
    var subject = "설 연휴 잘 보내세요! 🎉";
    var message = `안녕하세요,\n\n어느덧 설 연휴가 다가왔습니다. 한 해 동안 함께해 주셔서 감사드리며, 따뜻하고 행복한 명절 보내시길 바랍니다. 새해에는 더 큰 행운과 기쁨이 가득하길 기원합니다. 😊\n\n즐거운 설 연휴 보내세요!\n\n감사합니다.`;

    if (email === testEmail) {
      // ✅ [email protected]에게만 실제 발송
      MailApp.sendEmail({
        to: email,
        subject: subject,
        body: message
      });
      logProgress(`✅ [실제 발송] ${email}에게 메일이 전송되었습니다.`);
    } else {
      // ❌ 다른 이메일은 테스트 모드 유지
      logProgress(`[테스트 모드] 메일이 전송되지 않음 → 수신자: ${email}`);
    }
  });

  logProgress("✅ 모든 작업 완료!");
}

function formatDate(date) {
  return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate();
}

아래와 같이 메일 발신/수신 성공!

*메일 문구의 경우 이후에 GPT한테 요청하니 수정도 잘 해줬어요.

한국의 메시지 스크린 샷

다른 메일에 대해서도 작업이 잘 되고 있는건지 궁금..

function getSentEmailContactsAndSendTestEmails() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("이메일 목록");
  if (!sheet) {
    sheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("이메일 목록");
  }
  sheet.clear();
  sheet.appendRow(["이름", "메일주소", "메일발송횟수"]);

  var logSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("작업 로그");
  if (!logSheet) {
    logSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("작업 로그");
  }
  logSheet.clear();
  logSheet.appendRow(["시간", "진행 상황"]);

  function logProgress(message) {
    logSheet.appendRow([new Date().toLocaleString(), message]);
  }

  var isTestMode = true;

  logProgress("✅ 스크립트 실행 시작");

  var oneYearAgo = new Date();
  oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
  var query = 'after:' + formatDate(oneYearAgo);

  var emailCount = {};
  var start = 0;
  var batchSize = 500;

  while (true) {
    var threads = GmailApp.search(query, start, batchSize);
    if (threads.length === 0) break;

    logProgress(`🔍 ${threads.length}개의 이메일 스레드 검색됨`);

    threads.forEach(thread => {
      var messages = thread.getMessages();
      messages.forEach(message => {
        if (message.getFrom().includes(Session.getActiveUser().getEmail())) {
          var recipients = [].concat(
            message.getTo().split(","), 
            message.getCc() ? message.getCc().split(",") : [],
            message.getBcc() ? message.getBcc().split(",") : []
          );

          recipients.forEach(recipient => {
            var emailMatch = recipient.match(/<(.+?)>/); 
            var email = emailMatch ? emailMatch[1] : recipient.trim();
            var name = recipient.replace(/<.*?>/, "").trim();

            if (!name || name === email) {
              name = email.split("@")[0]; 
            }

            if (!emailCount[email]) {
              emailCount[email] = { name: name, count: 0 };
            }
            emailCount[email].count += 1;
          });
        }
      });
    });

    start += batchSize;
  }

  logProgress("✅ 데이터 처리 완료, 이메일 목록 스프레드시트에 저장 시작");

  Object.keys(emailCount).forEach(email => {
    sheet.appendRow([emailCount[email].name, email, emailCount[email].count]);

    var subject = "새해 복 많이 받으세요! 🎉";
    var message = `안녕하세요 ${emailCount[email].name}님,\n\n설 명절을 맞이하여 따뜻한 인사를 전합니다! 새해에는 건강과 행복이 가득하시길 바랍니다. 😊\n\n감사합니다.`;

    if (isTestMode) {
      logProgress(`[테스트 모드] 메일이 전송되지 않음 → 수신자: ${email}`);
    } else {
      MailApp.sendEmail({
        to: email,
        subject: subject,
        body: message
      });
      logProgress(`✅ 실제 메일 전송 완료 → ${email}`);
    }
  });

  logProgress("✅ 모든 작업 완료!");
}

function formatDate(date) {
  return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate();
}
한국어 텍스트가 장착 된 Google 스프레드 시트

결과와 배운 점

배운 점과 나만의 꿀팁을 알려주세요.

-확실히 구글 앱스크립트를 통해 > 구글 지메일로 업무를 연동하니 흐름이 매끄럽다. 내 요청사항을 잘 이해한다는 게 느껴지고, 시스템도 잘 구축되어 있는 느낌.

ex) 코드에 새로운 TASK를 추가하면(ex. 이제는 이 사람들에게 메일까지 전송해줘) 해당 작업에 대한 권한 요청이 온다.

-앱스크립트에서 문제가 생기면 차라리 새로 스프레드시트를 열어서 다시 작업하는 게 편할 수도..!

ex)

한국어로 된 메시지

과정 중에 어떤 시행착오를 겪었나요?

-원래 리멤버에 저장된 명함을 불러오고 싶었는데 크롤링에 어려움 있었음. (리스틀리 활용)

도움이 필요한 부분이 있나요?

-앱스크립트 코드 입력할 때 함수 관련 오류

-'이름' 칼럼 데이터를 깔끔히 불러오는 건 계속 어려웠어요.

앞으로의 계획이 있다면 들려주세요.

-구글 툴을 사용하지 않고, make 같은 자동화 툴과 연결해서 메일 전송 전체 단계를 자동화하고자 합니다.

도움 받은 글 (옵션)

참고한 지피터스 글이나 외부 사례를 알려주세요.

(내용 입력)

5
3개의 답글

뉴스레터 무료 구독

👉 이 게시글도 읽어보세요