기본 콘텐츠로 건너뛰기

[지메일 팁 1] 지메일에서 예약전송하는 방법

지메일이 좋은 이유는, 지메일에서 제공하는 기본적인 기능외에도 사용자들이 필요로하는 유용한 기능들을 직접 추가할 수 있는 Open API 들을 제공하고 있다는 것입니다. 3rd party 플러그인 모듈들도 다 이런 API 를 이용하여 개발한 것으로 보입니다.

지메일에서 예약 전송을 이용하는 방법에는 두가지가 있습니다.

목차




Google 스크립트와 Google 스프레드시트를 이용한 나만의 예약 전송 기능을 만들어 활용하는 방법


구글 문서도구 스프레드시트의 스크립트 기능을 활용하면 다양하고 유용한 유틸리티를 만들 수 있습니다. 이 문서에서 소개하는 내용은 간단하게 구글문서 도구에서 내 스프레드시트 (예, My Gmail Scheduler) 를 작성하여 공개된 구글 스크립트를 삽입하여 내 지메일을 예약 전송하는 방법에 대해서 소개하고자 합니다.

아래 스프레드시트(My Gmail Scheduler)에서 보는 것과 같이, 지메일에서 작성중인 이메일 (초안 상태)들에 대해서 원하는 시간에 예약 일정을 설정하여 ‘Gmail Scheduler’ 스크립트를 수행하면 예약전송 OK


예약전송 스크립트 스프레드시트 만드는 순서

1. 스프레드시트 ‘My Gmail Scheduler’ 생성
2. 스프데드시트 -> ‘도구’ -> 스크립트 편집기 수행


3. 아래 스크립트 코드 입력

/* This Google Script was written by Amit Agarwal */

/* Web: http://www.labnol.org/?p=24867            */

/* Apps Script Development: http://ctrlq.org      */

/* Please retain this message in your copy        */

function onOpen() {
 var ss = SpreadsheetApp.getActiveSpreadsheet();
 var menu = [
   {name: "Step 1: Authorize", functionName: "authorize_"},
   {name: "Step 2: Fetch Messages", functionName: "initialize_"},
   {name: "Step 3: Schedule Messages", functionName: "setSchedule_"},
   {name: "Cancel Pending Jobs", functionName: "cancelJobs_"}
 ];  
 ss.addMenu("Gmail Scheduler", menu);
}

function authorize_() {  
 deleteTriggers_();
 SpreadsheetApp.getActive().toast("Authorization successful. Please choose Step 2 to fetch the Gmail drafts into the spreadsheet.");
}

function fetchDraftMessages_() {
 var sheet = SpreadsheetApp.getActiveSpreadsheet();
 var drafts = GmailApp.getDraftMessages();
 if (drafts.length > 0) {
   var rows = [];    
   for (var i=0; i
     if (drafts[i].getTo() !== "") {
       rows.push([drafts[i].getId(), drafts[i].getTo(), drafts[i].getSubject(), "", ""]);
     }
   }
   sheet.getActiveSheet().getRange(2,1,rows.length,5).setValues(rows);
   sheet.toast("Excellent. Now please enter the date and time when you would like these messages to be delivered.");
 } else {
   sheet.toast("We could not find any messages in your Gmail drafts folder.");
 }
}

function cancelJobs_() {
 deleteTriggers_();
 var sheet = SpreadsheetApp.getActiveSheet();
 var data  = sheet.getDataRange().getValues();
 for (var row=1; row
   sheet.getRange("E"+(row+1)).setValue("Not Scheduled");
 }
 SpreadsheetApp.getActive().toast("There are no pending messages in queue.");
}

function initialize_() {
 deleteTriggers_();
 clearSheet_();  
 fetchDraftMessages_();
}

function clearSheet_() {
 var sheet = SpreadsheetApp.getActiveSheet();
 sheet.getRange(2, 1, sheet.getLastRow()+1, 5).clearContent();
}

function setSchedule_() {
 deleteTriggers_();
 try {
   var sheet = SpreadsheetApp.getActiveSheet();
   var data  = sheet.getDataRange().getValues();
   var time  = new Date().getTime();
   var code  = [];
   for (var row in data ) {     
     if (row != 0) {
       var schedule = data[row][3];
       if ( schedule !== "" ) {
         if ( schedule.getTime() > time ) {
           ScriptApp.newTrigger("sendMails")
           .timeBased()
           .at(schedule)
           .inTimezone(SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetTimeZone())
           .create();
           code.push("Scheduled");
         } else {
           code.push("Date is in the past");  
         }
       } else {
         code.push("Not Scheduled");
       }
     }
   }
   for (var i=0; i
     sheet.getRange("E" + (i+2)).setValue(code[i]);
   }
   SpreadsheetApp.getActiveSpreadsheet().toast("The mails have been scheduled. You can close this sheet. For help, contact amit@labnol.org", "Success", -1);
 } catch (e) {
   SpreadsheetApp.getActive().toast(e.toString());
 }
}

function deleteTriggers_() {
 var triggers = ScriptApp.getProjectTriggers();
 for (var i=0; i
   if (triggers[i].getHandlerFunction() === "sendMails") {
     ScriptApp.deleteTrigger(triggers[i]);
   }
 }
}


function sendMails() {
 try {
   var sheet = SpreadsheetApp.getActiveSheet();
   var data  = sheet.getDataRange().getValues();
   var time  = new Date().getTime();
   for (var row=1; row
     if (data[row][4] == "Scheduled") {             
       var schedule = data[row][3];
       if ( schedule != "" ) {
         if ( schedule.getTime() <= time ) {
           var status = dispatchDraft_(data[row][0]);
           sheet.getRange("E"+(row+1)).setValue(status);
         }
       }
     }
   }
 } catch (e) {
   SpreadsheetApp.getActive().toast(e.toString());
 }
}

function dispatchDraft_(id) {
 
 try {
   
   var message = GmailApp.getMessageById(id);
   
   if (message) {
     
     var body = message.getBody();
     var raw  = message.getRawContent();
     
     /* Credit - YetAnotherMailMerge */
     
     var regMessageId = new RegExp(id, "g");
     if (body.match(regMessageId) != null) {
       var inlineImages = {};
       var nbrOfImg = body.match(regMessageId).length;
       var imgVars = body.match(/]+>/g);
       var imgToReplace = [];
       if(imgVars != null){
         for (var i = 0; i < imgVars.length; i++) {
           if (imgVars[i].search(regMessageId) != -1) {
             var id = imgVars[i].match(/realattid=([^&]+)&/);
             if (id != null) {
               id = id[1];
               var temp = raw.split(id)[1];
               temp = temp.substr(temp.lastIndexOf('Content-Type'));
               var imgTitle = temp.match(/name="([^"]+)"/);
               var contentType = temp.match(/Content-Type: ([^;]+);/);
               contentType = (contentType != null) ? contentType[1] : "image/jpeg";
               var b64c1 = raw.lastIndexOf(id) + id.length + 3; // first character in image base64
               var b64cn = raw.substr(b64c1).indexOf("--") - 3; // last character in image base64
               var imgb64 = raw.substring(b64c1, b64c1 + b64cn + 1); // is this fragile or safe enough?
               var imgblob = Utilities.newBlob(Utilities.base64Decode(imgb64), contentType, id); // decode and blob
               if (imgTitle != null) imgToReplace.push([imgTitle[1], imgVars[i], id, imgblob]);
             }
           }
         }
       }
       
       for (var i = 0; i < imgToReplace.length; i++) {
         inlineImages[imgToReplace[i][2]] = imgToReplace[i][3];
         var newImg = imgToReplace[i][1].replace(/src="[^\"]+\"/, "src=\"cid:" + imgToReplace[i][2] + "\"");
         body = body.replace(imgToReplace[i][1], newImg);
       }
     }
     
     var options = {
       cc          : message.getCc(),
       bcc         : message.getBcc(),
       htmlBody    : body,
       replyTo     : message.getReplyTo(),
       inlineImages: inlineImages,
       attachments : message.getAttachments()
     }
     
     GmailApp.sendEmail(message.getTo(), message.getSubject(), body, options);
     message.moveToTrash();
     return "Delivered";
   } else {
     return "Message not found in Drafts";
   }
 } catch (e) {
   return e.toString();
 }
}

4. 스크립트 저장 및 실행
  1). 반드시 위 스크립트 코드를 입력한 후 저장 버튼을 클릭
  2). 저장 완료후, 실행->onOpen 함수를 수행  


5. 스크립트 onOpen 이 정상 수행이 되면, ‘내 지메일 예약 전송' 스프레드시트 메뉴에 ‘Gmail Scheduler’ 가 새롭게 생성이 됩니다.


6. 지메일에서 예약 전송하기 위한 메일 메시지들을 작성 후 전송하지 않고 초안(Draft) 상태 (임시보관함에 저장)로 저장하기



7. 스프레드시트에서 예약 전송 메시지 가져오기
  - 메뉴 -> Gmail Scheduler-> Step2: Fetch Messages 수행

8. 지메일에 예약전송 대상이 되는 메시지의 header 정보가 표시가 되고, 시트의 ‘D’ 컬럼에서 예약 전송하고자 하는 메시지를 선택 원하는 예약 일자를 입력 (예, 2014-08-01 08:00:00) 반드시 날짜 (YYYY-MM-DD) 와 시간 (HH:MM:SS) 을 입력해야 함


8번 과정을 통해서 지메일 예약이 정상 설정이 되면 스프레드시트는 닫기를 한 후, 나중에 다시 오픈하여 ‘E’ 컬럼을 확인하여 전송 상태 유무를 확인 할 수 있습니다. 정상으로 전송이 되면 ‘E’ 컬럼에 Delivered 표시가 됩니다.

크롬스토어에서 3rd party 앱을 설치하여 사용하는 방법이 있습니다.


크롬스토어에서 무료로 설치할 수 있는 예약전송의 대표적인 앱  Boomerang 을 크롬브라우저에서 설치하시면 됩니다.

크롬 브라우저에서 지메일 로그인 후, Boomerang 앱이 정상 설치 되면 아래 이미지에서와 같이 지메일 상단 오른쪽에 Boomerang 아이콘이 보여야 합니다.


편지쓰기시 하단에 ‘Send Later’ 라는 버튼이 보입니다. 이 버튼을 클릭하여 예약을 원하는 날짜및 시간을 입력하여 전송 하면 됩니다.

자세한 사항은 구글문서 참조

댓글

이 블로그의 인기 게시물

[알아두면 쓸모 있는 구글 문서 팁] 문서 공유시- 사용자 이름 대신에 익명의 동물이 표시 되는 이유와 동물 종류

구글 드라이브에는 다른 유사 서비스에서는 제공하지 않는 구글 만의 유니크한 기능들이 있다 구글 문서를  불특정 다수에게 전체 공개로 공유할 수 있습니다. 불특정인이 구글 문서에 접속한 경우 익명의 동물로 표시됩니다.  ' 웹에 공개' 또는 '링크가 있는 사용자' 공유 설정을 선택하면 인식할 수 없는 이름이나 익명의 동물이 표시될 수 있습니다. 파일에서 인식할 수 없는 이름을 볼 수 있는 몇 가지 이유는 다음과 같습니다. 메일링 리스트와 파일을 공유합니다. Google 계정이 없는 사용자와 파일을 공유하며, 그 사용자가 다른 사용자에게 공유 초대를 전달했습니다. 내 파일을 수정할 수 있는 누군가가 파일을 다른 사용자와 공유했습니다. 다른 사용자가 자신의 Google 계정 이름을 변경했습니다. 공유 설정 페이지에서 해당 사용자 이름 위로 마우스를 이동하여 이메일 주소를 확인하세요. 익명의 동물 다른 사용자에게 개별적으로 보기 또는 수정 권한을 부여하거나 메일링 리스트에 속해 있는 경우에만 사용자 이름이 표시됩니다. 파일 권한을 '링크가 있는 사용자'로 설정하면 파일을 보고 있는 사용자의 이름이 표시되지 않습니다. 대신 다른 사용자가 익명으로 라벨이 지정되어 표시되고 각 익명 사용자는 다양한 익명의 동물로 나열됩니다. 파일 권한을 '링크가 있는 사용자'로 설정했지만 특정 사용자와 파일을 공유하는 경우 파일을 공유한 사용자의 이름이 표시됩니다. 그 외 다른 사용자가 파일을 볼 때는 익명으로 나타납니다. 비공개 파일의 익명 동물 파일 권한을 '링크가 있는 사용자'로 설정한 다음 이를 '특정 사용자'로 변경하면 다음과 같은 경우 여러 익명의 동물이 표시될 수 있습니다. 누군가 파일을 여러 번 여는 경우에는 익명의 동물 목록에서 오래되고 연결이 끊긴 세션을 강제 종료하는 데 조금 시간이 걸릴 수 있습니다. 누군가 온...

[팁] Google Slide 프리젠테이션시 모든 한글폰트가 '굴림체' 로 바뀌는 현상을 해결한 크롬 확장 프로그램 소개

구글 문서도구인 구글 슬라이드를 이용하여 프리젠테이션을 많이 하는 분들을 위한 희소식 현재 구글 슬라이드에서는 슬라이드 편집시 사용한 고유 한글 폰트들은 프리젠테이션 모드로 전환할 경우는 모두 '굴림체' 로 바뀌어 표시가 되는 불편함이 있었습니다. 예). 슬라이드 편집에서 사용한 '궁서체' 한글 폰트는, 프리젠테이션 모드에서는 '굴림체'로 바뀌어 디스플레이됨 예). 슬라이드 편집 모드 - '궁서체' 폰트 사용 프리젠테이션 모드에서 '굴림체' 로 변경됨    따라서, 이러한 현상을 해결하는 크롬 확장 프로그램이 개발 되었습니다.  크롬 확장 프로그램 명 - ShowAsis 입니다. 크 롬 웹스토어 링크 -  https://goo.gl/PVPkZz 이 확장 프로그램을 사용하여 슬라이드 프리젠테이션을 하면, 편집 모드의 폰트 그대로 프리젠테이션시에도 그대로 한글 폰트로 디스플레이 됩니다. 단, 단점은 슬라이드가 애니메이션 슬라이드가 있는 경우는 애니메이션이 동작하지 않습니다. ----------------------- G Suite/Google Apps 전문 블로그 -  charlychoi.blogspot.kr 도서 '기업과 학교를 위한 구글크롬북'

구글 드라이브에서 내 파일이 갑자기 사라졌어요 [알아두면 쓸모 있는 구글 드라이브 팁]

  구글 드라이브에서 고아가 된(정리 되지 않은)  파일들에 대한 현상 및 복원 방법 및 공유 드라이브 활용 목차  배경 내 파일이나 폴더가 사라지고 (삭제된 것은 아님) 찾을 수 없는 현상 고아가 된 파일 (정리가 안된 파일) 을 찾아 내고 복원하는  방법 고아가 된 과정을 추적하는 방법 배경   구글 드라이브의 내 드라이브에서 협업을 위한 협업 폴더를 생성한 후 다른 팀원간들간에 공유하여 작업하는 동안 예기치 못하게 내가 생성한 파일들 또는 폴더가 갑자기 사라지는 현상이 발생 할 수 있습니다. 또한 이를 경험한 사용자들이 많이 있습니다.  내가 파일이나 폴더를 삭제하지 않았는데 불구하고 휴지통에도 없고, 내가 삭제한 기억도 없고, 이러한 현상이 발생할 경우에는 본 벡서서에서 설명하는 해당 폴더나 파일들이 ‘고아 (Orphaned)’ 가 된 상태가 되어있는게 분명합니다.  한글 도움말에는 ‘정리가 되지 않은 파일 (또는 분리가 된 파일)' 이라고 설명되어 있기도 합니다. 고아가 된 파일들은 어떠한 폴더에도 속하여 있지 않고, 내 휴지통에도 존재하지 않는 현상입니다. 그러나, 구글 드라이브에서는 용량을 계속 차지 않고 있는 상태입니다. 간혹 왜? 내가 삭제한 적이 없는데 파일이 없어졌거나 폴더가 보이질 않는 경우 당황하지 않고 이문서를 자세히 참조하면 해답을 찾을 수 있습니다.   내 파일이나 폴더가 사라지고 (삭제된 것은 아님) 찾을 수 없는 현상      1. 내 드라이브에서 Folder A를 생성하고 Folder A 안에 File A 를 생성 합니다. 나중에 File A를 삭제 하고, 그 이후에 Folder A 까지 삭제를 할 수 있습니다. 그후 휴지통에서 File A만 복원을 할 경우 삭제된 File A 를 복원하려고 하는데 File A가 존재하였던 상위 폴더 ‘Folder A’는 이미 삭제된 상태입니다. 이럴때 File A는 ...

[심층 리서치의 시대: Deep Research 활용 가이드 (백서)]

심층 리서치의 시대: Deep Research 활용 가이드 부제: Deep Research 와 Claude 융합 분석 방법 (백서) 정보의 홍수에서 진정한 통찰력을 찾는 방법: Deep Research와 Claude의 만남 혹시 이런 경험 있으신가요? 중요한 주제에 대해 검색을 하면 정보는 넘쳐나지만, 정작 의미 있는 통찰은 찾기 어려운 상황. 수많은 링크를 클릭하고, 여러 글을 읽어도 단편적인 정보만 얻을 뿐, 깊이 있는 이해는 얻지 못하는 답답함 말이죠. 우리가 직면한 정보 시대의 역설 디지털 세상에서 우리는 역설적 상황에 처해 있습니다. 정보는 넘쳐나지만 통찰력은 점점 더 희소해졌죠. 구글 같은 검색 엔진이 무한한 정보에 접근할 수 있게 해주었지만, 그 정보들을 의미 있는 지식으로 바꾸는 부담은 온전히 우리의 몫이 되었습니다. Deep Research와 Claude의 강력한 만남 이런 문제를 해결하기 위해 제가 발견한 혁신적인 방법이 있습니다. 바로 '2단계 심층 분석법'인데요, ChatGPT, Claude, Gemini 같은 생성형 AI와 Perplexity AI 같은 검색 기반 AI의 Deep Research 기능을 첫 단계로 활용하고, 그 다음 Claude의 고급 분석 능력을 통해 더 깊은 통찰력을 얻는 방식입니다. 1단계: Deep Research로 포괄적 정보 수집하기 Deep Research는 일반 검색과는 차원이 다릅니다. 도서관 사서가 관련 책이 있는 서가만 안내한다면, Deep Research는 그 책들을 모두 읽고, 핵심을 추출하여 출처가 검증된 맞춤형 보고서를 작성해주는 연구 조교와 같습니다. (*  Deep Research 이해하기 블로그 참고  **) 효과적인 Deep Research 질문을 위한 세 가지 핵심 프레임워크를 소개합니다: 다층적 질문 구조화 기법 : "[무엇에 대해] + [어느 기간/지역] + [어떤 측면] + [어떤 형태의 분석]" 예: "반도체 산업이 1990년대부터 지...

[2025년 초 AI 생태계 동향 분석]

  2025년 초 AI 생태계 동향 분석 AI 플랫폼 Poe의 사용자 데이터 기반 (2025년 3월 10일)  참조: https://poe.com/ko/blog/report-early-2025-ai-ecosystem-trends   Poe는 100개 이상의 AI 모델을 제공하는 플랫폼으로, 수백만 명의 사용자 데이터를 기반으로 AI 생태계 동향을 분석하고 있습니다. 이 보고서는 2024년 초부터 2025년 초까지의 AI 모델 사용 추세를 보여줍니다. AI 모델 생태계 확장 추이 (2024-2025) 2024년 초2024년 중반2024년 말2025년 초020406080모델 다양성 및 사용량 증가 (%) 카테고리 2024년 초 시장 주도 2025년 초 시장 주도 주요 변화 텍스트 AI GPT-4 (OpenAI) GPT-4o (OpenAI) Anthropic의 점유율 증가, DeepSeek 등장 이미지 AI DALL·E-3 (OpenAI) Imagen3 (Google) 모델 수 3개→25개, 시장 지형 완전 재편 비디오 AI Runway (독점) Veo2 (Google) 8개 이상 공급자로 확대, Google 급부상 텍스트 생성 AI 시장 분석 모델별 점유율 회사별 점유율 OpenAI 38.3%Anthropic 34.2%DeepSeek 7%Google 4%기타 16.5% 주요 발견점 OpenAI와 Anthropic이 텍스트 생성 AI 시장의 약 85%를 차지 2024년 6월 Claude 3.5 Sonnet 출시 이후 Anthropic의 사용량이 급증하여 OpenAI와 경쟁 심화 사용자들은 새로운 모델이 출시되면 빠르게 이전 버전에서 전환 (GPT-4→GPT-4o, Claude-3→Claude 3.5) DeepSeek-R1 및 -V3는 2024년 12월부터 7%의 점유율을 확보, 기존 오픈소스 모델보다 강세 Google의 Gemini 계열은 2024년 10월까지 성장했으나 이후 하락세 이미지 생성 AI 시장 분석 모델별 점유율 회사/그룹별 점...