如何使用谷歌应用脚​​本发送电子邮件草稿

Posted

技术标签:

【中文标题】如何使用谷歌应用脚​​本发送电子邮件草稿【英文标题】:How to send a draft email using google apps script 【发布时间】:2015-01-28 04:07:22 【问题描述】:

我正在使用 Google 应用程序脚本,并希望创建一个脚本,该脚本从草稿中提取邮件并在其标签为“send-tomorrow”的情况下发送它们。 查找带有特定标签的草稿非常简单:

 var threads = GmailApp.search('in:draft label:send-tomorrow');

但是我没有看到发送消息的 API! 我看到的唯一选择是: - 打开消息 - 提取正文/附件/标题/from/to/cc/bcc - 使用上述参数发送新消息 - 销毁之前的草稿

这看起来很烦人,我不确定嵌入图像、多个附件等是否能很好地工作......

有什么提示吗?

【问题讨论】:

【参考方案1】:

我看到的唯一选项是: - 打开消息 - 提取正文/附件/标题/from/to/cc/bcc - 使用上述参数发送新消息 - 销毁以前的草稿

这是 Amit Agarawa 的 this blog 的确切主题。他的脚本就像你描述的那样,但不处理内联图像。对于这些,您可以修改来自this article 的代码。

但是你是对的 - 如果你不能只发送愚蠢的东西,那么即使有草稿消息又有什么意义?!

我们可以使用 Google Apps 脚本中的 GMail API Users.drafts: send 发送草稿。以下独立脚本执行此操作,并处理必要的授权。

脚本

完整的脚本可在this gist 中找到。

/*
 * Send all drafts labeled "send-tomorrow".
 */
function sendDayOldDrafts() 
  var threads = GmailApp.search('in:draft label:send-tomorrow');

  for (var i=0; i<threads.length; i++) 
    var msgId = threads[0].getMessages()[0].getId();
    sendDraftMsg( msgId );
  



/**
 * Sends a draft message that matches the given message ID.
 * Throws if unsuccessful.
 * See https://developers.google.com/gmail/api/v1/reference/users/drafts/send.
 *
 * @param String     messageId   Immutable Gmail Message ID to send
 *
 * @returns Object               Response object if successful, see
 *                                 https://developers.google.com/gmail/api/v1/reference/users/drafts/send#response
 */
function sendDraftMsg( msgId ) 
  // Get draft message.
  var draftMsg = getDraftMsg(msgId,"json");
  if (!getDraftMsg(msgId)) throw new Error( "Unable to get draft with msgId '"+msgId+"'" );

  // see https://developers.google.com/gmail/api/v1/reference/users/drafts/send
  var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts/send'
  var headers = 
    Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
  ;
  var params = 
    method: "post",
    contentType: "application/json",
    headers: headers,
    muteHttpExceptions: true,
    payload: JSON.stringify(draftMsg)
  ;
  var check = UrlFetchApp.getRequest(url, params)
  var response = UrlFetchApp.fetch(url, params);

  var result = response.getResponseCode();
  if (result == '200')   // OK
    return JSON.parse(response.getContentText());
  
  else 
    // This is only needed when muteHttpExceptions == true
    var err = JSON.parse(response.getContentText());
    throw new Error( 'Error (' + result + ") " + err.error.message );
  



/**
 * Gets the current user's draft messages.
 * Throws if unsuccessful.
 * See https://developers.google.com/gmail/api/v1/reference/users/drafts/list.
 *
 * @returns Object[]             If successful, returns an array of 
 *                                 Users.drafts resources.
 */
function getDrafts() 
  var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts';
  var headers = 
    Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
  ;
  var params = 
    headers: headers,
    muteHttpExceptions: true
  ;
  var check = UrlFetchApp.getRequest(url, params)
  var response = UrlFetchApp.fetch(url, params);

  var result = response.getResponseCode();
  if (result == '200')   // OK
    return JSON.parse(response.getContentText()).drafts;
  
  else 
    // This is only needed when muteHttpExceptions == true
    var error = JSON.parse(response.getContentText());
    throw new Error( 'Error (' + result + ") " + error.message );
  


/**
 * Gets the draft message ID that corresponds to a given Gmail Message ID.
 *
 * @param String     messageId   Immutable Gmail Message ID to search for
 *
 * @returns String               Immutable Gmail Draft ID, or null if not found
 */
function getDraftId( messageId ) 
  if (messageId) 
    var drafts = getDrafts();

    for (var i=0; i<drafts.length; i++) 
      if (drafts[i].message.id === messageId) 
        return drafts[i].id;
      
    
  

  // Didn't find the requested message
  return null;



/**
 * Gets the draft message content that corresponds to a given Gmail Message ID.
 * Throws if unsuccessful.
 * See https://developers.google.com/gmail/api/v1/reference/users/drafts/get.
 *
 * @param String     messageId   Immutable Gmail Message ID to search for
 * @param String     optFormat   Optional format; "object" (default) or "json"
 *
 * @returns Object or String     If successful, returns a Users.drafts resource.
 */
function getDraftMsg( messageId, optFormat ) 
  var draftId = getDraftId( messageId );

  var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts'+"/"+draftId;
  var headers = 
    Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
  ;
  var params = 
    headers: headers,
    muteHttpExceptions: true
  ;
  var check = UrlFetchApp.getRequest(url, params)
  var response = UrlFetchApp.fetch(url, params);

  var result = response.getResponseCode();
  if (result == '200')   // OK
    if (optFormat && optFormat == "JSON") 
      return response.getContentText();
    
    else 
      return JSON.parse(response.getContentText());
    
  
  else 
    // This is only needed when muteHttpExceptions == true
    var error = JSON.parse(response.getContentText());
    throw new Error( 'Error (' + result + ") " + error.message );
  

授权

要使用 Google 的 API,我们需要为当前用户提供 OAuth2 令牌 - 就像我们为高级服务所做的那样。这是使用ScriptApp.getOAuthToken() 完成的。

将代码复制到您自己的脚本后,打开 Resources -> Advanced Google Services,打开 Google Developers Console 的链接,并为您的项目启用 Gmail API。

只要脚本包含至少一个需要用户权限的 GMailApp 方法,就会为 OAuthToken 正确设置身份验证范围。在这个例子中,这是由GmailApp.search() in sendDayOldDrafts() 处理的;但为了保险起见,您可以使用 API 直接在函数中包含不可访问的函数调用。

【讨论】:

已更新 - 功能齐全(哇!) 非常非常非常(等等...)干得好。已经是昨天了,但现在很完美!非常感谢我的朋友 ;-) 再次 +1【参考方案2】:

我使用GmailMessage.forward 方法做到了。

它适用于上传图片和附件,但我必须设置主题以避免前缀“Fwd:”和用户名,因为它只向收件人显示用户电子邮件。

我没有找到处理草稿的方法,所以我只删除了标签以防止再次发送。

脚本:

function getUserFullName()
  var email = Session.getActiveUser().getEmail();
  var contact = ContactsApp.getContact(email);
  return contact.getFullName();


function testSendTomorrow()
  var threads = GmailApp.search('in:draft label:send-tomorrow');

  if(threads.length == 0)
    return;
  

  var labelSendTomorrow = GmailApp.getUserLabelByName("send-tomorrow");

  for(var i = 0; i < threads.length; i++)
    var messages = threads[i].getMessages();
    for(var j = 0; j < messages.length; j++)
      var mssg = messages[j];
      if(mssg.isDraft())
        mssg.forward(mssg.getTo(), 
          cc: mssg.getCc(),
          bcc: mssg.getBcc(),
          subject: mssg.getSubject(),
          name: getUserFullName()
        );
      
    
    threads[i].removeLabel(labelSendTomorrow);
  

【讨论】:

非常聪明的方法! @Mogsdad 的解决方案仍然更完整,但您的解决方案肯定更简单(即使有些限制) 更新:今天可以添加线程[i].moveToTrash();并将草稿副本发送到 trach(您仍然会在已发送的邮件中保留另一份)【参考方案3】:

您可以搜索所有草稿,然后发送该特定草稿没问题。

function sendMessage(id)
  GmailApp.getDrafts().forEach(function (draft) 
    mes = draft.getMessage()
    if (mes.getId() == id) 
      draft.send()
    
  )

【讨论】:

【参考方案4】:

首先,GmailDraft 现在有一个可以直接调用的send() 函数。见:https://developers.google.com/apps-script/reference/gmail/gmail-draft#send()

他们的代码示例:

var draft = GmailApp.getDrafts()[0]; // The first draft message in the drafts folder
var msg = draft.send(); // Send it
Logger.log(msg.getDate()); // Should be approximately the current timestamp

其次,现在谷歌已经发布了预定发送,可能甚至不需要它。

    单击Send 旁边的箭头

    选择您的首选发送时间

【讨论】:

【参考方案5】:

更简单的替代方法是使用 gmail api 而不是 gmailApp:

function sendtestDraft(draftId)


  var request = Gmail.Users.Drafts.send(id : draftId,'me');


  Logger.log(request);


上面的函数示例在https://script.google.com 的gs 脚本中使用。 它需要 DraftId(而不是消息 Id)并且草稿将被发送。图片和附件都OK! 信息:https://developers.google.com/gmail/api/v1/reference/users/drafts/send

【讨论】:

我还认为@mogsdad 的上述解决方案使用了 API,我错了吗? 此解决方案是否消耗API quota?多少钱? send() 中的iduserId。请查看您提供的链接中的 API。 @evan 不,你错了。我的例子确实有效。 userId 是“me” - 来自文档: userId:“用户的电子邮件地址。特殊值 me 可用于指示经过身份验证的用户”。 id 是“草稿的不可变 ID”。 - 请阅读我提供的链接中的 API。 @user2677034 - 你完全正确。我重新查看了该链接,它确实将 draftId 作为输入。我一定是打开了另一个窗口并感到困惑。很好的发现!【参考方案6】:

我是新来的,没有足够的“声誉”来发表评论,所以无法评论 Mogsdad 的原始答案,所以我不得不创建一个新答案:

我已经调整了 Mogsdad 的解决方案,使其也支持回复/转发现有线程,而不仅仅是全新的消息。

要在现有线程上使用它,您应该首先创建回复/转发,然后才标记线程。我的代码还支持多个标签并设置这些标签。

我为它创建了一个新的要点,将 Mogsdad 的分叉,在这里:https://gist.github.com/hadasfester/81bfc5668cb7b666b4fd6eeb6db804c3

我仍然需要在文档中添加一些屏幕截图链接,否则这是可以使用的,我自己一直在使用它。希望对您有用。

也在这里内联:

/**
 * This script allows you to mark threads/drafts with a predetermined label and have them get sent the next time your trigger
 * sets off. 
 * 
 * Setup instructions:
 * 1. Make a copy of this script (File -> Make a copy)
 * 2. Follow the "Authorization" instructions on https://***.com/a/27215474. (If later during setup/testing you get
 *    another permissions approval dialog, approve there as well).
 * 2. I created two default labels, you can edit/add your own. See "TODO(user):" below. After that, to create them in gmail,
 *    choose "setUpLabel" function above and click the play button (TODO: screenshot). Refresh your gmail tab, you should see
 *    the new labels.
 * 3. Click the clock icon above (TODO: screenshot) and set time triggers, e.g. like so: (TODO: screenshot)
 * 4. I recommend also setting up error notifications: (TODO: screenshot).
 * 
 * Testing setup:
 * When you're first setting this up, if you want to test it, create a couple 
 * of drafts and label them. Then, in this 
 * script, select "sendWeekStartDrafts" or "sendTomorrowDrafts" in the function dropdown 
 * and press play. This manually triggers the script (instead of relying on the 
 * timer) so you can see how it works. 
 *
 * Usage instructions:
 * 1. To get a draft sent out on the next trigger, mark your draft with the label you chose.
 *    NOTE: If your draft is a reply to a thread, make sure you first create the draft and only then set the label on the
 *    thread, not the other way around.
 * That's it! Upon trigger your draft will be sent and the label will get removed from the thread.
 * 
 * Some credits and explanation of differences/improvements from other existing solutions:
 * 1. This script was adapted from https://***.com/a/27215474 to also support replying existing threads, not only
 *    sending brand new messages.
 * 2. Other solutions I've run into are based on creating a new message, copying it field-by-field, and sending the new one,
 *    but those have many issues, some of which are that they also don't handle replies and forwards very elegantly.
 *
 * Enjoy!
 **/

var TOMORROW_LABEL = '!send-tomorrow';
var WEEK_START_LABEL = '!send-week-start';
// TODO(user): add more labels here.

/**
 * Set up the label for delayed send!
 **/
function setUpLabels() 
  GmailApp.createLabel(TOMORROW_LABEL);
  GmailApp.createLabel(WEEK_START_LABEL);
  // TODO(user): add more labels here.


function sendTomorrowDrafts() 
  sendLabeledDrafts(TOMORROW_LABEL);


function sendWeekStartDrafts() 
  sendLabeledDrafts(WEEK_START_LABEL);


// TODO(user): add more sendXDrafts() functions for your additional labels here.

/*
 * Send all drafts labeled $MY_LABEL.
 * @param String     label   The label for which to send drafts.
 */
function sendLabeledDrafts(label) 
  var threads = GmailApp.search('in:draft label:' + label);

  for (var i=0; i<threads.length; i++) 
    var thread = threads[i];
    var messages = thread.getMessages();
    var success = false;
    for (var j=messages.length-1; j>=0; j--) 
      var msgId = messages[j].getId();
      if (sendDraftMsg( msgId )) 
        success = true;
      
    
    if (!success)  throw Error( "Failed sending msg" ) ;
    if (success) 
      var myLabel = GmailApp.getUserLabelByName(label);
      thread.removeLabel(myLabel);
    
  


/**
 * Sends a draft message that matches the given message ID.
 * See https://developers.google.com/gmail/api/v1/reference/users/drafts/send.
 *
 * @param String     messageId   Immutable Gmail Message ID to send
 *
 * @returns Object               Response object if successful, see
 *                                 https://developers.google.com/gmail/api/v1/reference/users/drafts/send#response
 */
function sendDraftMsg( msgId ) 
  // Get draft message.
  var draftMsg = getDraftMsg(msgId,"json");
  if (!getDraftMsg(msgId)) return null;
  
  // see https://developers.google.com/gmail/api/v1/reference/users/drafts/send
  var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts/send'
  var headers = 
    Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
  ;
  var params = 
    method: "post",
    contentType: "application/json",
    headers: headers,
    muteHttpExceptions: true,
    payload: JSON.stringify(draftMsg)
  ;
  var check = UrlFetchApp.getRequest(url, params)
  var response = UrlFetchApp.fetch(url, params);

  var result = response.getResponseCode();
  if (result == '200')   // OK
    return JSON.parse(response.getContentText());
  
  else 
    // This is only needed when muteHttpExceptions == true
    return null;
  



/**
 * Gets the current user's draft messages.
 * Throws if unsuccessful.
 * See https://developers.google.com/gmail/api/v1/reference/users/drafts/list.
 *
 * @returns Object[]             If successful, returns an array of 
 *                                 Users.drafts resources.
 */
function getDrafts() 
  var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts';
  var headers = 
    Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
  ;
  var params = 
    headers: headers,
    muteHttpExceptions: true
  ;
  var check = UrlFetchApp.getRequest(url, params)
  var response = UrlFetchApp.fetch(url, params);

  var result = response.getResponseCode();
  if (result == '200')   // OK
    return JSON.parse(response.getContentText()).drafts;
  
  else 
    // This is only needed when muteHttpExceptions == true
    var error = JSON.parse(response.getContentText());
    throw new Error( 'Error (' + result + ") " + error.message );
  


/**
 * Gets the draft message ID that corresponds to a given Gmail Message ID.
 *
 * @param String     messageId   Immutable Gmail Message ID to search for
 *
 * @returns String               Immutable Gmail Draft ID, or null if not found
 */
function getDraftId( messageId ) 
  if (messageId) 
    var drafts = getDrafts();

    for (var i=0; i<drafts.length; i++) 
      if (drafts[i].message.id === messageId) 
        return drafts[i].id;
      
    
  

  // Didn't find the requested message
  return null;



/**
 * Gets the draft message content that corresponds to a given Gmail Message ID.
 * See https://developers.google.com/gmail/api/v1/reference/users/drafts/get.
 *
 * @param String     messageId   Immutable Gmail Message ID to search for
 * @param String     optFormat   Optional format; "object" (default) or "json"
 *
 * @returns Object or String     If successful, returns a Users.drafts resource.
 */
function getDraftMsg( messageId, optFormat ) 
  var draftId = getDraftId( messageId );

  var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts'+"/"+draftId;
  var headers = 
    Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
  ;
  var params = 
    headers: headers,
    muteHttpExceptions: true
  ;
  var check = UrlFetchApp.getRequest(url, params)
  var response = UrlFetchApp.fetch(url, params);

  var result = response.getResponseCode();
  if (result == '200')   // OK
    if (optFormat && optFormat == "JSON") 
      return response.getContentText();
    
    else 
      return JSON.parse(response.getContentText());
    
  
  else 
    // This is only needed when muteHttpExceptions == true
    return null;
  

【讨论】:

以上是关于如何使用谷歌应用脚​​本发送电子邮件草稿的主要内容,如果未能解决你的问题,请参考以下文章

谷歌应用脚​​本:您无权调用提示

如何将Outlook发送的电子邮件唯一匹配到其对应的Outlook草稿(MAPI MailItem)

使用谷歌应用脚​​本从电子表格数据中检索行

使用谷歌应用脚​​本从 MYSQL 数据库中获取数据时,谷歌电子表格的单元格中缺少某些值

如何使用 IMAP 发送邮件?

谷歌应用脚​​本:是不是可以在发送共享通知的同时限制文件的共享设置?