亚马逊AWS官方博客

Amazon SES 应对 Gmail 和 Yahoo 的新垃圾邮件过滤政策实践

背景

为了保护用户接收到高质量的邮件,Gmail 和 Yahoo Mail 宣布在 2024 年 2 月起开始实施一项新的垃圾邮件过滤政策,新的政策限制对象为每天发送超过 5000 封邮件的发信人以及被大量标记为垃圾邮件的发信人,具体的要求有以下 3 点:

  1. 更严格地遵循域身份验证:要求发送大量邮件的发件人实施严格的电子邮件身份验证,需要正确设置 SPF、DMARC 和 DKIM。
  2. 轻松取消订阅:用户应该能够轻松取消订阅不需要的电子邮件,发件人应支持提供一键取消订阅商业电子邮件的功能,并在两天内处理取消订阅请求。
  3. 监控垃圾投诉率并保持在3%的阈值:将强制实施明确的垃圾邮件率阈值,发件人必须保持在该阈值以下,以确保收件人不会收到不必要的邮件。

本博客旨在为您提供详细的步骤和方法应对 Gmail 和 Yahoo 的新垃圾邮件过滤政策。

具体实现

1. 域身份验证

Amazon Simple Email Service(Amazon SES)使用简单邮件传输协议(SMTP)发送电子邮件。由于 SMTP 本身不提供任何身份验证,因此垃圾邮件发送者可以发送声称来自其他任何人的电子邮件,同时隐藏其真实来源。垃圾邮件发送者通过伪造电子邮件标头并欺骗源 IP 地址,可误导收件人相信其收到的电子邮件是真实的。大多数转发电子邮件流量的 ISP 会采取措施来评估电子邮件是否合法,ISP 采取的一项措施是确定电子邮件是否已经过身份验证,身份验证要求发件人证明他们是用来发送电子邮件的账户所有者。在某些情况下,ISP 会拒绝转发未经身份验证的电子邮件,为了确保实现最佳送达率,建议您对电子邮件进行身份验证。

  • 发件人策略框架(SPF)和域名密钥识别邮件(DKIM)

DKIM 和 SPF 是电子邮件身份验证的两种基本形式。DKIM 使用非对称加密来签名和验证您的电子邮件,是为了解决邮件欺诈问题和邮件被拒收、进入垃圾箱等问题而制定的一种标准。SPF 允许您列出所有被授权代表您的域发送电子邮件的 IP 地址,用于防范垃圾邮件。

  • 使用 DMARC 对电子邮件进行身份验证

基于域名的邮件认证、报告和一致性(DMARC)是一种电子邮件认证协议,它使用发件人策略框架(SPF)和 DomainKeys 识别邮件(DKIM)来检测电子邮件欺骗和网络钓鱼。为了遵守 DMARC 的要求,邮件必须通过 SPF 或 DKIM 进行身份验证,但理想情况下,当两者都与 DMARC 一起使用时,你将确保你的电子邮件发送得到尽可能高的保护。

以下是 DMARC 的工作原理,请参考:

完成域身份验证,可以使用以下工具进行验证 https://bimigroup.org/bimi-generator/(BIMI 要求更严格,需要至少 policy=quarantine or reject)或者 https://domain-checker.valimail.com/dmarc

2. 轻松取消订阅

为收件人提供一种方便的方式来取消订阅,发件人需要额外添加 RFC 2369 和 RFC 8058 定义的一键取消订阅标头,如下图,可以看到 Gmail 的发件人旁边会显示“Unsubscribe”的字样,用户可以很方便的取消相关的订阅。该按钮会引导客户直接进到邮件订阅列表管理的功能。这些标头使收件人更容易取消订阅,从而降低了收件人将邮件标记为垃圾邮件的投诉率。

RFC 2369 定义了“List-Unsubscribe”标头字段,用于指定退订邮件列表的方式,如邮件地址或网页链接。

RFC 8058 对此进行了扩展,引入了“List-Unsubscribe-Post”标头字段,支持通过 HTTP POST 请求进行退订操作。

2.1 使用 SES 订阅管理功能实现一键退订

SES 利用订阅列表和订阅管理功能来帮助用户遵守 Gmail 和 Yahoo Mail 的发送要求。有两种方式实现,第一种是当您在 SendEmail 操作请求中的 ListManagementOptions 中指定 contactListNametopicName 时,Amazon SES 通过该功能自动启用每一封外发电子邮件中的取消订阅链接;第二种是使用 SMTP 方式发送 SES 邮件时,在 X-SES-LIST-MANAGEMENT-OPTIONS 的标头中指定 contactListName 和 topicName,Amazon SES 将自动为该邮件增加取消订阅的链接。当用户通过该链接取消订阅后,Amazon SES 将不再向该联系人发送对应主题的邮件。

注意:每个 AWS 账户只允许使用一个联系人列表,一个列表可以包含最多 20 个主题。

您需要在发送电子邮件时指定联系人列表。通过 List-Unsubscribe 标头和 ListManagementOptions 脚注链接进行的订阅管理将得到相应的处理。

当电子邮件包含以下标头时,将启用通过取消订阅链接进行的订阅管理:

List-Unsubscribe

List-Unsubscribe-Post

其电子邮件客户端可识别这些标头的收件人将看到“Unsubscribe”(取消订阅)链接,并可以通过该链接取消订阅,但无法选择取消订阅哪些主题,只能取消订阅该电子邮件所属的主题。

有关 List-Unsubscribe 标头的更多信息,请参阅 RFC 2369,有关 List-Unsubscribe-Post 标头,请参阅 RFC 8058

创建联系人列表

{
    "ContactListName": "ExampleContactListName",
    "Description": "Creating a contact list example",
    "Topics": [
     {
         "TopicName": "Sports",
         "DisplayName": "Sports Newsletter",
         "Description": "Sign up for our free newsletter to receive updates on all sports.",
         "DefaultSubscriptionStatus": "OPT_OUT"
     },
     {
         "TopicName": "Cycling",
         "DisplayName": "Cycling newsletter",
         "Description": "Never miss a cycling update by subscribing to our newsletter.",
         "DefaultSubscriptionStatus": "OPT_IN"
     },
     {
         "TopicName": "NewProducts",
         "DisplayName": "New products",
         "Description": "Hear about new products by subscribing to this mailing list.",
         "DefaultSubscriptionStatus": "OPT_IN"
     },
     {
         "TopicName": "DailyUpdates",
         "DisplayName": "Daily updates",
         "Description": "Start your day with sport updates, Monday through Friday.",
         "DefaultSubscriptionStatus": "OPT_OUT"
     }
    ]
}
aws sesv2 create-contact-list --cli-input-json file://CONTACT-LIST-JSON

创建联系人

{
    "ContactListName": "ExampleContactListName",
    "EmailAddress": "example@amazon.com",
    "UnsubscribeAll": false,
    "TopicPreferences": [
        {
            "TopicName": "Sports",
            "SubscriptionStatus": "OPT_IN"
        }
    ],
    "AttributesData": "{\"Name\": \"John\", \"Location\": \"Seattle\"}"
}
aws sesv2 create-contact --cli-input-json file://CONTACT-JSON

在使用 SMTP 接口发送电子邮件时,通过添加 X-SES-LIST-MANAGEMENT-OPTIONS 标头,指定为上一步创建的 ContactListName 及 TopicName。X-SES-LIST-MANAGEMENT-OPTIONS: {contactListName}; topic={topicName}

以下为 java 以 SMTP 协议发送邮件时的部分代码示例:

MimeMessage message = new MimeMessage(session); 
// Set the X-SES-LIST-MANAGEMENT-OPTIONS header
String contactListName = "ExampleContactListName"; // Replace with your contact list name
String topicName = "Sports"; // Replace with your topic name
message.addHeader("X-SES-LIST-MANAGEMENT-OPTIONS", "ContactListName=" + contactListName + ", TopicName=" + topicName);

在邮件的正文内容中,通过插入 {{amazonSESUnsubscribeUrl}} 占位符的方式,可以让 Amazon SES 将占位符代替为相应的取消订阅链接。在接收到邮件后,您可以查看到邮件正文中具有相关的链接。您可以最多两次包含该占位符。如果使用了两次以上,那么仅替换前两次出现的占位符。

2.2 使用添加邮件 Header 实现

当您不使用 SES 订阅管理功能时可以使用添加邮件 header 的方式实现退订,您需要在发出的邮件中包括这两个 header:

List-Unsubscribe-Post: List-Unsubscribe=One-Click

List-Unsubscribe: <https://example.com/unsubscribe/example>

当前 SES API 可以用 SendRawEmail 或者 SMTP 的方式去加这 2 个自定义的 header,如果您使用 Amazon Pinpoint 发送邮件,也可以使用这种方法实现一键退订的功能,通过邮件中的取消订阅链接可以将收件人重定向到登录页面,以便收件人确认其退出偏好;需要注意的是,您需要处理收件人取消订阅的请求,并且在2天内确保退订用户在未来不会再接受到类似的电子邮件。

以下 python 示例代码展示了添加 header 方式实现一键退订功能:

response = sesv2.send_email(
    FromEmailAddress='example.com <support@example.com>',
    Destination={'ToAddresses': ['customer@destination.com']},
    ConfigurationSetName='ConfigSet',
    Content={
      "Simple": { 
         "Body": { 
            "Html": { 
               "Charset": "UTF-8",
               "Data": "Welcome to SES"
            },
            "Text": { 
             "Charset": "UTF-8",
             "Data": "Welcome to SES"
          }
         },
         "Subject": { 
            "Charset": "UTF-8",
            "Data": "Welcome to SES"
         },         
         "Headers": [ 
            { 
               "Name": "List-Unsubscribe",
               "Value": "<https://example.com/?address=x&topic=x>"
            },
            {
              "Name": "List-Unsubscribe-Post",
              "Value": "List-Unsubscribe=One-Click"            
            }
         ]
      }
    }
)

如果收件人的邮箱提供商支持列表取消订阅的电子邮件收件人,比如 Gmail 和 Yahoo,将会在发件人详细信息旁边看到一个取消订阅超链接,如下图所示。

3. 监控邮件投诉率

邮箱提供商将要求所有发件人将垃圾邮件投诉率保持在 0.3% 以下,以避免其电子邮件被邮箱提供商视为垃圾邮件。以下步骤概述了 SES 客户如何满足垃圾邮件投诉率要求:

  1. 使用 Google Postmaster Tools:Google Postmaster Tools 可以监控其发送到 Gmail 收件人的垃圾邮件投诉率,Gmail 建议垃圾邮件投诉率保持在 1% 以下。
  2. 启用 Amazon SES VDM:在您的 Amazon SES 账户中启用 Virtual Deliverability Manager (VDM) 虚拟可达性管理器(VDM),可以使用 VDM 监控许多邮箱提供商的退信和投诉率,需要注意的是开启 VDM 会有额外成本。
  3. 使用配置集:除了使用不同域名进行不同邮件活动发送外,可以使用 SES 配置集监控发送活动并进行更精细粒度实施限制,如果垃圾邮件投诉率超过您的容忍阈值,甚至可以自动暂停配置集的发送。

3.1 使用 Postmaster Tools 监控 SES 邮件送达率

Postmaster Tools 可以监控其发送到 Gmail 收件人的垃圾邮件投诉率,建立并维护良好的发件人声誉,而反馈循环(Feedback Loop,简称 FBL)正是帮助发件人实现这一目标的重要机制,FBL 可以作为一种让收件人向发件人报告垃圾邮件投诉的方式,当收件人将某封电子邮件标记为垃圾邮件时,ISP 会将此投诉反馈给发件人或其电子邮件服务提供商;通过这种方式,发件人可以及时了解哪些电子邮件活动可能存在问题,从而采取纠正措施,避免进一步的投诉和声誉损失,这对于维护健康的发件人声誉至关重要,因为良好的声誉不仅能提高电子邮件的送达率,还能确保营销活动的效果。

通过 Postmaster Tools 监控 SES 邮件送达率的实现原理是 SES 嵌入了一个名为 Feedback-ID 的 header,其中包含唯一标识帐户和 SenderID 的参数(标识符),Feedback-ID header 由四个参数组成,用冒号分隔:a:b:c:SenderId

header 参数 说明
a 第一个参数,可以通过 EmailTag ses:feedback-id-a 进行自定义
b 第二个参数,可以通过 EmailTag ses:feedback-id-b 进行自定义
c 第三个参数,不可重写,SES 用于识别 Sender 使用的 Account 信息
SenderID 第四个参数,不可重写,标识邮件发件人的唯一参数,始终为 “AmazonSES”

Feedback-ID 注意事项

  • 当通过 Amazon SES 发送电子邮件时,Feedback-ID 需要包含一个可用于匹配营销活动或一批电子邮件的标识符,而不是用来区分单个收件人
  • 此约束有助于保持一致的发件人信誉,使用 Feedback-ID 充当分组机制,改善 Postmaster tools 等工具中的可送达性监控和故障排除
  • 标识符必须是唯一的,并且在字段之间不重复

Feedback-ID 示例

CampaignIDX:CustomerID2:1.us-west-2.TDQeKqHkSNfQztk25wIeVIGTuNmGDud4r1l7dUlxOio=:AmazonSES

如果出现垃圾邮件率异常,每个标识符都可用于独立分析垃圾邮件百分比;SES 允许用户使用 EmailTag SES:Feedback-ID-a 和 SES:Feedback-ID-b 设置 Feedback-ID header 中的 a 部分和 b 部分,以下 Python 代码展示了使用 Boto3 发送 SES 邮件包含 Feedback-ID 的 header:

import boto3
from botocore.exceptions import ClientError

def send_email(region_name):
    # Create a new SES client
    ses = boto3.client('sesv2', region_name=region_name)

    # Replace sender and recipient values
    SENDER = "Sender Name <sender@example.email>"
    RECIPIENT = "recipient@example.com"
    CONFIGURATION_SET = "SES_Config_Set"
    SUBJECT = "Amazon SES Test (SDK for Python)"
    BODY_TEXT = "Amazon SES Test (Python)\r\nThis email was sent with Amazon SES using the AWS SDK for Python (Boto)."
    BODY_HTML = """<html>
    <head></head>
    <body>
      <h1>Amazon SES Test (SDK for Python)</h1>
      <p>This email was sent with
        <a href='https://thinkwithwp.com/ses/'>Amazon SES</a> using the
        <a href='https://thinkwithwp.com/sdk-for-python/'>
          AWS SDK for Python (Boto)</a>.</p>
    </body>
    </html>"""
    CHARSET = "UTF-8"

    try:
        # Send email
        response = ses.send_email(
            FromEmailAddress=SENDER,
            Destination={'ToAddresses': [RECIPIENT]},
            ConfigurationSetName=CONFIGURATION_SET,
            Content={
                "Simple": {
                    "Subject": {
                        "Charset": CHARSET,
                        "Data": SUBJECT
                    },
                    "Body": {
                        "Text": {
                            "Charset": CHARSET,
                            "Data": BODY_TEXT
                        },
                        "Html": {
                            "Charset": CHARSET,
                            "Data": BODY_HTML
                        }
                    },
                    "Headers": [
                        {
                            "Name": "List-Unsubscribe",
                            "Value": "<https://unsubscribe.example.email/?address=recipient@example.com&topic=topic1>"
                        },
                        {
                            "Name": "List-Unsubscribe-Post",
                            "Value": "One-Click"
                        }
                    ]
                }
            },
            EmailTags=[
                {
                    'Name': 'ses:feedback-id-a',
                    'Value': 'campaign1'
                },
                {
                    'Name': 'ses:feedback-id-b',
                    'Value': 'line-of-business'
                }
            ] #the ses:feedback-id-a and ses:feedback-id-b are specified as a list using EmailTags
        )
        print("Email sent! Response:", response)
        print("Message ID:", response['MessageId'])

    except ClientError as e:
        print(e.response['Error']['Message'])

# Call the function to send the email
send_email(region_name='us-west-2')  # Specify the region here

通过 Postmaster Tools 仪表板查看 FBL 结果,定期监控您的垃圾邮件率和反馈循环,使用这些数据来优化您的电子邮件内容和发送方式。

结论

本博客内容旨在 SES 应对 Gmail 和 Yahoo 的新垃圾邮件过滤政策,通过遵循这些步骤,您可以提高电子邮件的送达率,减少垃圾邮件投诉,并保持良好的发件人声誉,希望可以帮助您能够更好的将邮件送达到您的客户。

附录

https://docs.thinkwithwp.com/zh_cn/ses/latest/dg/send-email-authentication-dmarc.html

https://thinkwithwp.com/cn/blogs/messaging-and-targeting/an-overview-of-bulk-sender-changes-at-yahoo-gmail/

https://thinkwithwp.com/cn/blogs/messaging-and-targeting/how-to-enable-one-click-unsubscribe-email-with-amazon-pinpoint/

https://thinkwithwp.com/cn/blogs/messaging-and-targeting/understanding-google-postmaster-tools-spam-complaints-for-amazon-ses-email-senders/

本篇作者

陈汉卿

亚马逊云科技解决方案架构师,负责基于亚马逊云科技云计算方案的咨询、架构设计及落地,拥有多年移动互联网研发经验,在云原生微服务以及云迁移等方向有丰富的实践经验。

张凌钧

亚马逊云科技技术客户经理,负责企业级客户的架构和成本优化、技术支持等工作。在加入 AWS 之前就职于百度、360,拥有多年的服务开发、平台运维经验。

王文巍

亚马逊云科技资深解决方案架构师,10 多年互联网企业研发、团队管理经验,主要专注于电商、新零售、社交媒体等领域。