如何判断通过 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。
本来想着下面这个方案的,但是漏洞太多,我想我一定是走错了。
- 通过 Gmail 发送电子邮件(发送失败的电子邮件)API ---> 成功通过
- 收件箱收到退回邮件
- 扫描电子邮件以查找包含退回的电子邮件 headers
- 试着找出被退回的原始电子邮件。
问题
- Gmail api returns Gmail 邮件 ID,不是实际的邮件 ID
- 必须不断monitor/poll收件箱以查看是否有退回的电子邮件
- 甚至可以根据 header 的存在进行搜索吗?
- 每个电子邮件提供商似乎都有不同的退回 headers
- headers 可能不表示原始消息 ID
我的其他一些想法是:
- 搜索主题中包含字符串 "Undeliverable" 的电子邮件?
- 不使用 gmail rest api 发送,因为退回跟踪不可行。也许改用 SMTP api?
通过 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));
}
我正在通过 Gmail API 发送电子邮件,想知道邮件何时退回。我该怎么做?
据我了解,退回的电子邮件通常包含某种 header 表示退回,例如:
X-Failed-Recipients: zzzzzzzzzasdfasdfadfa@gmail.com
但是,似乎并不总是 header 指示退回的原始邮件 ID。
本来想着下面这个方案的,但是漏洞太多,我想我一定是走错了。
- 通过 Gmail 发送电子邮件(发送失败的电子邮件)API ---> 成功通过
- 收件箱收到退回邮件
- 扫描电子邮件以查找包含退回的电子邮件 headers
- 试着找出被退回的原始电子邮件。
问题
- Gmail api returns Gmail 邮件 ID,不是实际的邮件 ID
- 必须不断monitor/poll收件箱以查看是否有退回的电子邮件
- 甚至可以根据 header 的存在进行搜索吗?
- 每个电子邮件提供商似乎都有不同的退回 headers
- headers 可能不表示原始消息 ID
我的其他一些想法是:
- 搜索主题中包含字符串 "Undeliverable" 的电子邮件?
- 不使用 gmail rest api 发送,因为退回跟踪不可行。也许改用 SMTP api?
通过 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));
}