如何判断通过 Gmail REST API 发送的电子邮件是否已退回?

How to tell if an email sent via Gmail REST API has bounced?

我正在通过 Gmail API 发送电子邮件,想知道邮件何时退回。我该怎么做?

据我了解,退回的电子邮件通常包含某种 header 表示退回,例如:

X-Failed-Recipients: zzzzzzzzzasdfasdfadfa@gmail.com

但是,似乎并不总是 header 指示退回的原始邮件 ID。

本来想着下面这个方案的,但是漏洞太多,我想我一定是走错了。

  1. 通过 Gmail 发送电子邮件(发送失败的电子邮件)API ---> 成功通过
  2. 收件箱收到退回邮件
  3. 扫描电子邮件以查找包含退回的电子邮件 headers
  4. 试着找出被退回的原始电子邮件。

问题

我的其他一些想法是:

通过 Gmail API 发送时被退回的邮件会从邮件守护进程 (mailer-daemon@googlemail.com) 得到响应。您可以不断检查用户的消息,看看是否收到来自守护程序的新消息。

确保自上次检查以来以秒为单位存储时间戳,这样下次就不会得到任何讨厌的重复项。

query = from:mailer-daemon@googlemail.com after:<TIME_SINCE_EPOCH_IN_SECONDS>

GET https://www.googleapis.com/gmail/v1/users/me/messages?q=from%3Amailer-daemon%40googlemail.com+after%3A1437055051&access_token={YOUR_API_KEY}

响应:

{
 "messages": [
  {
   "id": "14e97f7ed03b7e88",
   "threadId": "14e97f7ea9b794a4"
  },
 ]
}

我反弹了!让我们获取整封邮件并对其进行解码,并获得您提到的 Message-ID。

GET https://www.googleapis.com/gmail/v1/users/me/messages/14e97f7ed03b7e88?fields=payload%2Fbody%2Fdata&access_token={YOUR_API_KEY}

响应:

{
 "payload": {
  "body": {
   "data": "RGVsA0K..."
  }
 }
}

将邮件从 URL 安全版本转换为常规 base64(将所有“-”替换为“+”,将“_”替换为“/”),然后对其进行 base64 解码,我们得到:

atob("RGVsA0K...".replace(/\-/g, '+').replace(/\_/g, '/'));

解码邮件:

"Delivery to the following recipient failed permanently:

     sadsadsadas@sadsads.asdsad

Technical details of permanent failure: 
DNS Error: Address resolution of sadsads.asdsad. failed: Domain name not found

----- Original message -----

.
.
.

Received: from 292824132082.apps.googleusercontent.com named unknown by
 gmailapi.google.com with HTTPREST; Thu, 16 Jul 2015 13:44:43 -0400
from: example@gmail.com
Date: Thu, 16 Jul 2015 13:44:43 -0400
Message-ID: <this_is_it@mail.gmail.com>
Subject: Subject Text
To: sadsadsadas@sadsads.asdsad
Content-Type: text/plain; charset=UTF-8

The actual message text goes here

这里有消息 ID!让我们收到退回的电子邮件吧!

query = rfc822msgid:<this_is_it@mail.gmail.com>;

GET https://www.googleapis.com/gmail/v1/users/me/messages?q=rfc822msgid%3A%3CCADsZLRzOs1wT4B5pgR7oHHdbjkQhuaCQQs8CEckhLwVw73QFEQ%40mail.gmail.com%3E&key={YOUR_API_KEY}

响应:

{
 "messages": [
  {
   "id": "14e97f7ea9b794a4", // <-- Here is the message that bounced!
   "threadId": "14e97f7ea9b794a4"
  }
 ],
}

这是使用后端技术并尝试遵循当前文档指南的方法。因为它出现在之后:和之前:标签只支持日期而不是日期+时间。因此,虽然 EPOCH 时间以上可能已经在较早的代码上工作,但在当前 API 中还需要做更多的工作。下面的一些内容尚未添加到我在 github 上的测试项目中。但给出一个想法:

我们要求它回头查看任何早于一天的退回邮件,然后解析 headers 以查找接收日期 - 将字符串转换为实际日期并与 -20 分钟前的结果进行比较,任何更早的未添加的内容到清单

List verifyBounceList (Gmail service) {
        List foundResults=[]

        Date date = new Date()
        use (groovy.time.TimeCategory) {
            date= date -20.minute
        }
        Date yesterday = new Date()-1
        def bounceRecords = listMessagesMatchingQuery(service,'me','from:mailer-daemon@googlemail.com after:'+yesterday.format('YYYY/MM/dd'))
        bounceRecords?.each {
            Message message = getMessage(service,'me',it.id)
            String receivedDateString = message.getPayload().headers?.find{it.name=='Received'}.value.split(';')[1].trim()
            SimpleDateFormat df = new SimpleDateFormat('EEE, dd MMM yyyy HH:mm:ss z (Z)')
            Date receivedDate=df.parse(receivedDateString)
            if (receivedDate>date) {
                foundResults<<[bouncedRecord:it,mapRecord:message]
            }
        }
        return foundResults
    }

Message getMessage(Gmail service, String userId, String messageId) throws IOException {
    Message message = service.users().messages().get(userId, messageId).execute()
    ///System.out.println("Message snippet: " + message.getSnippet())
    return message
}
    /**
 * Simply does a query in given mailbox 
 * used to query for mail failures
 * @param service
 * @param userId
 * @param query
 * @return
 * @throws IOException
 */
List<Message> listMessagesMatchingQuery(Gmail service, String userId, String query) throws IOException {
    ListMessagesResponse response = service.users().messages().list(userId).setQ(query).execute()

    List<Message> messages = new ArrayList<Message>()
    while (response.getMessages() != null) {
        messages.addAll(response.getMessages())
        if (response.getNextPageToken() != null) {
            String pageToken = response.getNextPageToken()
            response = service.users().messages().list(userId).setQ(query).setPageToken(pageToken).execute()
        } else {
            break;
        }
    }
    for (Message message : messages) {
        //System.out.println(message.toPrettyString());
    }

    return messages;
}

以上作为迭代返回结果的列表返回:

<g:if test="${instance.size()>0}"> 
        <h2>Bounces found : ${instance.size()}</h2><br/>
        <div class="errors">
        <g:each in="${instance}" var="failed">
            ${failed.bouncedRecord} --> ${failed.mapRecord?.id} ${failed.mapRecord?.getSnippet()} ${ }<br/>
            <g:each in="${failed.mapRecord.getPayload().headers}" var="a">
            ${a }<br/>---
            </g:each>
        </g:each>

当您通过

发送消息时
service.users().messages().send(userId, message).execute();

它将return一个Message。您可以使用它的 threadId 检查您是否收到了对该消息的任何回复。

这里有一个检查是否被退回的简单方法(发送后延迟 1 秒):

public static boolean isBounced(Gmail service, String threadId) throws IOException {
    List<Message> list = service.users().messages().list("me")
                        .setQ("from=mailer-daemon@googlemail.com")
                       .execute().getMessages();

    return list.stream().anyMatch(msg -> msg.getThreadId().equals(threadId));
}