#16 转载:邮件的 Return-Path 头是什么

2020-07-31

We can all agree that email communication is essential for successful businesses. However, for your emails to be successful and produce conversions, they have to land in the inbox. 
我们都同意电子邮件通信对于成功的企业至关重要。但是,要使您的电子邮件成功并产生转化,它们必须进入收件箱。

But what happens when emails don’t make it to the inbox? Where do they go when they bounce? How are they processed?
但是,当电子邮件没有进入收件箱时会发生什么?当它们反弹时,它们会去哪里?它们是如何处理的?

Enter return-path.
使用 Return-Path 。

Return-path is a hidden email header that indicates where and how bounced emails will be processed. This header, also referred to as a bounce address or reverse path, is an SMTP address that is separate from your original sending address, and is used specifically for collecting and processing bounced messages.
Return-Path 是一个隐藏的电子邮件标头,指示处理退回电子邮件的位置和方式。此标头(也称为退回地址或反向路径)是独立于原始发送地址的 SMTP 地址,专门用于收集和处理退回的邮件。

Having a clear return-path system in place is incredibly important for your email program. It acts as a safeguard, protecting senders by providing a separate location for processing bounced emails. Your original sending inbox isn’t crowded by those “failed delivery” emails and that bounced messages are kept organized and together. Having a clear, organized return-path for bounced messages can also help your email deliverability and maintain your sending reputation.
拥有清晰的 Return-Path 系统对于您的电子邮件程序非常重要。它作为一种保护措施,通过提供单独的位置来处理退回的电子邮件来保护发件人。您的原始发送收件箱不会被那些“传递失败”的电子邮件所拥挤,并且退回的邮件保持井井有条并在一起。为退回的邮件提供清晰、有组织的 Return-Path 还有助于您的电子邮件送达率并保持您的发送声誉。

Why is return-path important?(为什么 Return-Path 很重要?)

Return-path is an important tool to have at your disposal, especially for mass email sends. Let’s say you’re sending an email blast about an offer your company is promoting to your entire email list. While we don’t want to see bounced emails, the reality is that messages can and do bounce for a variety of reasons. 
Return-Path 是您可以使用的重要工具,尤其是对于群发电子邮件。假设您正在向整个电子邮件列表发送一封关于您的公司正在推广的优惠的电子邮件爆炸。虽然我们不希望看到退回的电子邮件,但现实情况是,由于各种原因,邮件可以并且确实会退回。

When you’re sending to large groups, you can get tens, maybe even hundreds of bounced messages depending on the size and nature of your campaign. These “failed delivery” messages then come back to haunt and crowd your original sending inbox. Instead, by having an established return-path, those messages are processed and stored separately in their own specified inbox.
当您向大型群组发送邮件时,您可能会收到数十甚至数百封退回的邮件,具体取决于广告系列的规模和性质。然后,这些“传递失败”消息会再次困扰并挤占您原来的发送收件箱。相反,通过建立 Return-Path ,这些邮件将单独处理并存储在其自己指定的收件箱中。

Return-path also helps with your deliverability and sending reputation by helping to validate your identity as a sender (i.e. whether or not you’re sending spam). Because return-path is a SMTP address, it can be used by servers and inbox providers to decide how or if they want to filter your messages. Having a properly set-up return-path can help provide credibility for your messages and subsequently you, the sender, which in turn boosts your sending reputation.
Return-Path 还有助于验证您作为发件人的身份(即您是否发送垃圾邮件),从而帮助您提高送达率和发送信誉。由于 Return-Path 是 SMTP 地址,因此服务器和收件箱提供商可以使用它来决定如何或是否要过滤您的邮件。正确设置 Return-Path 有助于为您的消息提供可信度,进而为您(发件人)提供可信度,从而提高您的发送声誉。

How return-path works(Return-Path 的工作原理)

Return-path works by directing where bounced messages should go when they cannot be delivered. It is usually set up by a developer or email platform provider, but can be customized using the Domain Authentication doc in our Knowledge Center
Return-Path 的工作原理是指示退回的邮件在无法传递时应去哪里。它通常由开发人员或电子邮件平台提供商设置,但可以使用 我们知识中心的域身份验证文档进行自定义。

When a server or inbox provider receives your message, they validate your identity as a sender as well as your sending reputation before pushing you through to your intended recipients’ inboxes. 
当服务器或收件箱提供商收到您的邮件时,他们会验证您作为发件人的身份以及您的发送信誉,然后再将您推送到目标收件人的收件箱。

In this validation process, DMARC and return-path work together to get you through these filters. DMARC examines your message to confirm that the domain provided in the “sent from” field matches the domain provided in the return-path field, which helps to validate your identity as a sender. Once these domains have been confirmed and matched by DMARC, you’ll have an easier time getting through filters set by servers and inbox providers.
在此验证过程中,DMARC 和 Return-Path 协同工作,帮助您通过这些过滤器。DMARC 会检查您的邮件,以确认“发件人”字段中提供的域与 Return-Path 字段中提供的域匹配,这有助于验证您作为发件人的身份。一旦DMARC确认并匹配了这些域名,您就可以更轻松地通过服务器和收件箱提供商设置的过滤器。

There are two types of bounced emails: hard bounces and soft bounces. Hard bounces occur when there are permanent issues with a recipient, including an invalid email address or typo in your mailing list. Soft bounces are more temporary and usually occur when there’s a problem with a recipient’s inbox, including file size or attachments issues or the possibility of a recipient having a full inbox. 
退回电子邮件有两种类型:硬退回邮件和软退回邮件。当收件人存在永久性问题(包括邮件列表中的电子邮件地址无效或拼写错误)时,就会发生硬退回。软退回邮件是临时性的,通常在收件人的收件箱出现问题时发生,包括文件大小或附件问题,或者收件人的收件箱可能已满。

When a message hard bounces, the general best practice is to check that there are no typos in the recipient’s address. If there are none, you should remove the address from your mailing list. Keeping email addresses that hard bounce can damage your reputation as a sender and affect your deliverability in the long run. 
当邮件被硬退回时,一般的最佳做法是检查收件人的地址中是否有拼写错误。如果没有,您应该从邮件列表中删除该地址。保留硬退回的电子邮件地址可能会损害您作为发件人的声誉,并从长远来看影响您的送达率。

When an email soft bounces, you have a little bit more wiggle room than with a hard bounce. Email addresses that soft bounce can be kept in your mailing list for future campaigns, but you’ll want to watch them to see if they bounce again. If they continue to bounce, they should be removed from your mailing list.
当电子邮件软退回时,与硬退回相比,您有更多的回旋余地。软退回的电子邮件地址可以保留在您的邮件列表中,以备将来的广告系列使用,但您需要观察它们,看看它们是否会再次退回。如果它们继续反弹,则应将其从您的邮件列表中删除。

#14 邮件安全:SPF

2020-02-11

简介

SMTP 会话中,发行方会通过 MAIL FROM 命令告诉收信方,这封邮件是谁发出的。

但是,由于邮件系统的设计上没有考虑如何对垃圾邮件进行有效防范,收信方需要一个机制来判断这封邮件的来源是否可靠。因此,人们提出了 SPF 方案:

  1. 每个邮件服务都需要为自己的发信域名在 DNS 中配置一下 SPF 记录(TXT 类型),记录值是出信 IP。
  2. 收信方拿到 MAIL FROM 地址之后,使用发信方 IP 地址和发信域 DNS 中记录的 IP 地址做一个比对。如果匹配,说明来源正确,否则,这封邮件可以被视为垃圾邮件,直接返回投递失败(硬退)。

该方案 2006 年提交到 Network Working Group 成为草案,然后 2014 年成为建议标准

注意:SPF 只是验证了邮件确实是从发信域所在网络投递出来的,但是不能防止邮件伪造和欺诈。应该结合其他技术,如 DKIM(DomainKeys Identified Mail)和 DMARC(Domain-based Message Authentication, Reporting, and Conformance),一起增强电子邮件的安全性。

SPF 记录示例

dig +short txt qq.com
"v=spf1 include:spf.mail.qq.com -all"

dig +short txt spf.mail.qq.com
"v=spf1 include:qq-a.mail.qq.com include:qq-b.mail.qq.com include:qq-c.mail.qq.com include:biz-a.mail.qq.com include:biz-b.mail.qq.com include:biz-c.mail.qq.com include:biz-d.mail.qq.com -all"

dig +short txt qq-{a..c}.mail.qq.com biz-{a..d}.mail.qq.com
"v=spf1 ip4:101.226.139.0/25 ip4:101.91.43.0/25 ip4:101.91.44.128/25 ip4:112.64.237.128/25 ip4:116.128.173.0/25 ip4:121.51.40.128/25 ip4:121.51.6.0/25 ip4:162.62.52.214 ip4:162.62.55.67 ip4:162.62.57.0/24 ip4:162.62.58.211 ip4:162.62.58.216 -all"
"v=spf1 ip4:162.62.58.69 ip4:162.62.63.194 ip4:180.163.24.128/25 ip4:183.2.187.0/25 ip4:203.205.221.128/25 ip4:203.205.251.0/25 ip4:210.51.43.0/25 ip4:58.246.222.128/25 ip4:58.250.143.128/25 ip4:61.241.55.128/25 -all"
"v=spf1 ip4:113.108.92.0/25 ip4:121.14.77.0/25 ip4:81.69.217.16/28 ip4:54.164.151.162 -all"
"v=spf1 ip4:114.132.122.39 ip4:114.132.123.192 ip4:114.132.124.171 ip4:114.132.125.233 ip4:114.132.197.227 ip4:114.132.224.180 ip4:114.132.233.22 ip4:114.132.58.0/24 ip4:43.155.65.254 ip4:114.132.62.0/24 ip4:106.55.200.77 -all"
"v=spf1 ip4:114.132.63.24 ip4:114.132.64.0/26 ip4:114.132.65.219 ip4:43.155.67.158 ip4:114.132.67.179 ip4:114.132.73.137 ip4:114.132.74.132 ip4:43.154.209.5 ip4:43.154.197.177 ip4:43.154.155.102 ip4:43.155.80.173 ip4:43.154.221.58 -all"
"v=spf1 ip4:54.204.34.129 ip4:54.204.34.130 ip4:54.243.244.52 ip4:52.205.10.60 ip4:35.173.142.173 ip4:54.207.22.56 ip4:54.207.19.206 ip4:54.254.200.92 ip4:54.254.200.128 ip4:54.92.39.34 ip4:54.206.16.166 ip4:54.206.34.216 ip4:114.132.75.215 -all"
"v=spf1 ip4:52.59.177.22 ip4:18.194.254.142 ip4:18.132.163.193 ip4:18.169.211.239 ip4:13.245.186.79 ip4:13.245.218.24 ip4:15.184.224.54 ip4:15.184.82.18 ip4:114.132.76.87 ip4:114.132.77.159 ip4:114.132.78.196 ip4:114.132.79.153 ip4:43.154.54.12 -all"

语法

  1. "v=spf1":指定 SPF 记录的版本,目前只有 SPFv1 版本。

  2. 机制(Mechanisms):SPF 使用机制来指定哪些服务器被授权发送邮件。常用的机制有:

    • "all":定义了所有情况下的默认结果。可以是 "+all"(通过验证)或 "-all"(不通过验证)。例如,"-all" 表示除非匹配其他通过机制,否则所有情况都不通过验证,即严格拒绝伪造邮件。
    • "ip4" 和 "ip6":指定允许发送邮件的 IPv4 和 IPv6 地址。例如:"ip4:192.0.2.1" 表示允许来自 192.0.2.1 的服务器发送邮件。
    • "a" 和 "mx":允许域名的 A 记录和 MX 记录指定的主机发送邮件。
    • "include":允许引用其他域名的 SPF 记录。例如:"include:example.com" 表示允许按照 example.com 的 SPF 记录来验证。
  3. 修饰符(Modifiers):修饰符可以在机制之后指定,用于调整验证的结果。常用的修饰符有:

    • "+":显示通过验证。
    • "-":显示不通过验证,相当于 "-all"。
    • "~":软失败,邮件可能被接受,但会标记为不可信。
    • "?":中性结果,邮件可能被接受,但不影响验证的结果。

SPF 记录示例:

v=spf1 ip4:192.0.2.1 a mx ~all

以上示例表示允许 IP 地址为 192.0.2.1 的服务器、A 记录和 MX 记录指定的主机发送邮件,但验证结果为软失败,邮件可能会被接受,但标记为不可信。

DNS SPF 类型

IANA 设计了一种专门的 DNS 类型用来记录 SPF 信息,但是采用率非常低。
可以忽略,继续使用 TXT 类型来保存 SPF 信息。

参考资料与拓展阅读

  • Sender Policy Framework
  • RFC4408, Experimental / 2006, Sender Policy Framework (SPF) for Authorizing Use of Domains in E-Mail, Version 1
  • RFC7208, Standards Track / 2014, Sender Policy Framework (SPF) for Authorizing Use of Domains in Email, Version 1
  • http://www.open-spf.org/Tools/
  • Golang 库:https://github.com/mileusna/spf

    package main
    
    import (
       "net"
    
       "github.com/mileusna/spf"
    )
    
    func main() {
       // optional, set DNS server which will be used by resolver.
       // Default is Google's 8.8.8.8:53
       spf.DNSServer = "1.1.1.1:53"
    
       ip := net.ParseIP("123.123.123.123")
       r := spf.CheckHost(ip, "domain.com", "name@domain.com", "");
       // returns spf check result
       // "PASS" / "FAIL" / "SOFTFAIL" / "NEUTRAL" / "NONE" / "TEMPERROR" / "PERMERROR"
    
       // if you only need to retrive SPF record as string from DNS
       spfRecord, _ := spf.LookupSPF("domain.com")
    }
    
  • Golang 库:https://github.com/asggo/spf

    package main
    
    import "github.com/asggo/spf"
    
    func main() {
    
          SMTPClientIP := "1.1.1.1"
          envelopeFrom := "info@example.com"
    
          result, err := spf.SPFTest(SMTPClientIP, envelopeFrom)
          if err != nil {
                   panic(err)
          }
    
          switch result {
          case spf.Pass:
                   // allow action
          case spf.Fail:
                   // deny action
          }
       //...
    }
    

#13 邮件安全:DKIM

2020-02-10

DomainKeys Identified Mail,域名密钥识别邮件
作用是使用非对称加密(公钥 + 私钥)对邮件内容进行签名,防止伪造和篡改。

历史

  • 2004 年,雅虎 DomainKeys 和思科 Identified Internet Mail 合并为 DKIM。
  • 2007 年 2 月,DKIM 被列入互联网工程工作小组(IETF)的标准提案(Proposed Standard),并于同年 5 月成为正式标准(Standards Track)。

示例

DKIM-Signature: v=1; a=rsa-sha256; d=example.net; s=brisbane;
     c=relaxed/simple; q=dns/txt; i=foo@eng.example.net;
     t=1117574938; x=1118006938; l=200;
     h=from:to:subject:date:keywords:keywords;
     z=From:foo@eng.example.net|To:joe@example.com|
       Subject:demo=20run|Date:July=205,=202005=203:44:08=20PM=20-0700;
     bh=MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=;
     b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZ
              VoG4ZHRNiYzR
Tag Required Meanning
v version
a signing algorithm
d Signing Domain Identifier (SDID)
s selector
c - canonicalization algorithm(s) for header and body
q - default query method
i - Agent or User Identifier (AUID)
t recommended signature timestamp
x recommended expire time
l - body length
h header fields - list of those that have been signed
z - header fields - copy of selected header fields and values
bh body hash
b signature of headers and body

示例 2

邮件中的 DKIM-Signature 头:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=mail.instagram.com;
    s=s1024-2013-q3; t=1674163477;
    bh=6RXa/HYJQNKpB5PIGtLn7v1NE/4T5FaqxBLWNHVRZu8=;
    h=Date:To:Subject:From:MIME-Version:Content-Type;
    b=VAY3x16QtXeH1rQxu6eEbzhfgZl69m1sG9XzN3ym4FbWiMg+K+IfMGF4yszGYk8yO
     YXAAJZuQfG45pjthISDDSwhhBK0WGgufQ8ofnzhNUN9WT/okEATC+JfzksS9w2Ts4V
     ALa/4HHXnikQV5AFNJJNJIvWMN/fJ5c49nLkW024=

域名中的 DKIM 公钥:

dig TXT +short s1024-2013-q3._domainkey.mail.instagram.com
"k=rsa; t=s; h=sha256; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7twdVo+BW8Pv2poU5129KYmE6npHdxUU8fktUKTE9TNovCvLy5LVjYc3TQcUFjOH" "VaZ89ZCjmpAcrA2QnTEKZ/2QWV56gn6bWdFW4SFxnQdHjguBZQykfKe5KTxy2a/OxuA0x2dHfdnYfw7RVzr4uednpKcWJy4Rl3gM6XB1zDwIDAQAB"

Python 编程中的应用

准备工作

  1. selector 选择 s2020 (随便)
  2. 生成密钥对
# 方法一:使用 OpenDKIM
sudo apt install -y opendkim-tools
opendkim-genkey --help
opendkim-genkey -D . -d markjour.com -s s2020

# 方法二:使用 OpenSSL
openssl genrsa -out dkim-markjour-s2020.pem 2048
openssl rsa -in dkim-markjour-s2020.pem -pubout -outform der 2>/dev/null | openssl base64 -A > dkim-markjour-s2020.pub
  1. 配置 DNS

s2020._domainkey.markjour.com

"v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjnfmXLuyBt0Tus/Bdr87GpLqcRCZX7UC61OiPZ1Y3MG42qBcdEAMJfu7qop7KOLL8cywTRxiX39ehmf0ZovAXH1KkijiX/16tkI3cO9T6KS4vyr0Ip3fsGgNgjn5rH3M5AZAmbym6DIzYrtpTiAKgLYmFLALd9SLi/OhFIltWK+QJhaJgcuWUXCzlry01Fdsv1qj28WdZ6PQbQrSffc1qzkvOEOlmZXwWjQfg5X4E3DR4WKenC6f5WdcJeXk4pUeBOdQDoEM+4uCk4S6cN3OuYEQbvVmfQ5RAlCODccx7lJemWZZnlIf+03FppUEEMENZ8tu3iixD24m2q9wDLDL5QIDAQAB
  • v 版本,只有一种选择:DKIM1,必须放最前面,可以忽略
  • h 哈希算法,sha1 或者 sha256
  • k 密钥类型,只有一种选择:rsa,可以忽略
  • n 注释,可以忽略
  • p 公钥(ASN.1 DER-encoded + Base64)
  • s 服务类型,默认 *,表示所有服务,可以忽略
  • t 逗号隔开的标记
    • y 测试
    • s DKIM 签名中的 i= 域名必须是 d= 域名完全相同(子域也不行)。

参考:

签名

headers = {'aaa': '123', 'bbb': '456', 'ccc': '789'}

校验签名

mail = """
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=qq.com; s=s0907;
    t=1331524836; bh=Pqr4lbxMcef/3IqsXx/edT0iwPe18N7n8qKmQSnLio8=;
    h=X-QQ-SSF:X-QQ-Spam:X-QQ-BUSINESS-ORIGIN:X-Originating-IP:
     X-QQ-STYLE:X-QQ-mid:From:To:Sender:Subject:Mime-Version:
     Content-Type:Content-Transfer-Encoding:Date:X-Priority:Message-ID:
     X-QQ-MIME:X-Mailer:X-QQ-Mailer:X-QQ-ReplyHash;
    b=hF3hXt429Mp9WUJx9wQQYYk32EABCQST/OmV+dI+vJ/XIidVkc6fsh8l/vBz/optb
     MDp0XIupHHkUozz6jwMryhHd/ZNjLNtBBAIOgl1wH7R016x8uTtDQink5uIPH+5
X-QQ-SSF: 0000000000010060
X-QQ-Spam: true
X-QQ-BUSINESS-ORIGIN: 2
X-Originating-IP: 61.151.148.196
X-QQ-STYLE:
X-QQ-mid: bizmail6t1331524835t2734595
From: "=?gb18030?B?wffE6g==?=" <thinkphp@qq.com>
To: "=?gb18030?B?uvqwug==?=" <ninedoors@126.com>
Sender: liuchen@topthink.net
Subject: =?gb18030?B?UmU60rvOu1RQZXK1xNLJu/M=?=
Mime-Version: 1.0
Content-Type: multipart/alternative;
    boundary="----=_NextPart_4F5D74E3_DF6406C0_249C5176"
Content-Transfer-Encoding: 8Bit
Date: Mon, 12 Mar 2012 12:00:35 +0800
X-Priority: 3
Message-ID: <tencent_519100472CAA258E750FA58D@qq.com>
X-QQ-MIME: TCMime 1.0 by Tencent
X-Mailer: QQMail 2.x
X-QQ-Mailer: QQMail 2.x
X-QQ-ReplyHash: 3949397671
""".strip('\n')

#12 Python DKIM 签名

2019-04-16
  • pydkim
  • https://pypi.org/project/pydkim/
  • https://hewgill.com/pydkim/
  • NOTE: This page describes the last release of pydkim from 2008. The latest version is a fork found at dkimpy in Launchpad and is under active development.
  • 最新版本是 2008/06 发布的 v0.3
  • dkimpy
  • https://pypi.org/project/dkimpy/
  • https://launchpad.net/dkimpy
  • 最新版本是昨天发布的 v0.9.2
Date Version
2023-07-28 1.1.5
2023-05-12 1.1.4
2023-04-30 1.1.3
2023-04-09 1.1.2
2023-03-10 1.1.1
2023-02-25 1.1.0
2023-04-30 1.0.6
2020-08-09 1.0.5
2020-04-06 1.0.4
2020-01-15 1.0.3
2019-12-31 1.0.2
2019-12-15 1.0.1
2019-12-09 1.0.0
2019-12-24 0.9.6
2019-10-07 0.9.5
2019-09-25 0.9.4
2019-08-09 0.9.3
2019-04-15 0.9.2
2018-12-09 0.9.1
2018-10-30 0.9.0

说明

默认签名字段(28 个):

cc, content-description, content-id, content-transfer-encoding, content-type,
date,
from,
in-reply-to,
list-archive, list-help, list-id, list-owner, list-post, list-subscribe, list-unsubscribe,
message-id, mime-version,
references, reply-to, resent-cc, resent-date, resent-from, resent-message-id, resent-sender, resent-to,
sender, subject,
to
d = dkim.DKIM('')
print(b', '.join(d.should_sign | d.frozen_sign))
b'list-unsubscribe, content-id, list-id, mime-version, resent-date, sender, cc, reply-to, content-type, list-owner, resent-message-id, resent-cc, resent-from, to, content-description, date, list-post, in-reply-to, content-transfer-encoding, from, references, list-help, subject, list-archive, resent-sender, list-subscribe, message-id, resent-to'
print(b', '.join(sorted(d.should_sign | d.frozen_sign)))
b'cc, content-description, content-id, content-transfer-encoding, content-type, date, from, in-reply-to, list-archive, list-help, list-id, list-owner, list-post, list-subscribe, list-unsubscribe, message-id, mime-version, references, reply-to, resent-cc, resent-date, resent-from, resent-message-id, resent-sender, resent-to, sender, subject, to'

示例

准备实验用的密钥对。

openssl genpkey -algorithm RSA -out /tmp/private_key.pem
openssl rsa -in /tmp/private_key.pem -check
openssl rsa -pubout -in /tmp/private_key.pem -out /tmp/public_key.pem
import dkim

domain = 'mail.markjour.com'
selector = 's20190416'

with open('/tmp/private_key.pem', 'rb') as f:
    privkey = f.read().strip()
# dkim.parse_pem_private_key(privkey)

message = """
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=utf-8
Date: Mon, 24 Sep 2018 12:31:21 +0000 (UTC)
From: Admin <no-reply@mail.markjour.com>
Mime-Version: 1.0
Subject: Hello World
Message-ID: <n4F5zz24LXvYqPHVrZLPJokasT7MlLxYQx6g>
Reply-To: sender@mail.markjour.com
To: kwicoo@gmail.com
List-Unsubscribe: <mailto:unsubscribe@mail.markjour.com?p=Ahi2DRmdOnTdpsDzPClCPqbpwmFyjvGJV2xfJGWqw6eFEKRwI402QeoSsFrArTw1s48A59f60pLl0x71ojsQSWERnp3aMZA6YvEw>
X-SMTP-ID: c89cf6a5-22b7-4d1a-9bce-9f91a6be1bfb

HELLO WORLD
""".strip().encode()
# dkim.rfc822_parse(message)
# print(dkim.DKIM(message).default_sign_headers())
# [b'Content-Transfer-Encoding', b'Content-Type', b'Date', b'From', b'Mime-Version', b'Subject', b'Message-ID', b'Reply-To', b'To', b'List-Unsubscribe', b'From']

signature = dkim.sign(message, selector.encode(), domain.encode(), privkey)
print(signature.decode())
# DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=mail.markjour.com;
#  i=@mail.markjour.com; q=dns/txt; s=s20190416; t=1555392275;
#  h=content-transfer-encoding : content-type : date : from :
#  mime-version : subject : message-id : reply-to : to : list-unsubscribe
#  : from; bh=qg03cTlGc4OH4uPv7BGgoUyhgh23r+o1O6qzYOLixvA=;
#  b=sJ09G6hHPaP6AMp2mqUXjEZ+BfUFz0o6nbpXWxJ4/OG0o9ZwPSj8aJibZtJjTKP3k/TR/
#  6SD543V8iNw+JwwM+XLOUZa0iduK+QkedccqNl5Hcfc9UI/U11NoHz76B3csL9KE9tb40jF
#  mlLCuVUjci4HlOfEoKF8Ame8yWDHXVoNS/YT9/OSSc5q5q+qp6OX6PvzzxDomCHC6kbhOdv
#  Yc/KEXrMQ1JQ971pRUBNQK3eN7bV7g1BwXuMEuhdwDa4aZ4YYcakKywo4Oey7bIy1E7evZN
#  5rUitRExLH4dQNrhxoZd4c3QOjd4ROTwseAaMN10U/egzDXjcw2q0UUC1UKQ==

append_headers = [b'x-smtp-id']

d = dkim.DKIM(message)
include_headers = d.default_sign_headers()
include_headers.extend(append_headers)
signature = d.sign(selector.encode(), domain.encode(), privkey, include_headers=include_headers)
print(signature.decode())
# DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=mail.markjour.com;
#  i=@mail.markjour.com; q=dns/txt; s=s20190416; t=1555392275;
#  h=content-transfer-encoding : content-type : date : from :
#  mime-version : subject : message-id : reply-to : to : list-unsubscribe
#  : from : x-smtp-id; bh=qg03cTlGc4OH4uPv7BGgoUyhgh23r+o1O6qzYOLixvA=;
#  b=MTSeE8X3R+8bn+kkJaX5j/OKPMe+sdombmmwK5zME3SHBqiOLbxCwOGyh3qJKXdLpJlEg
#  pBnsDmNEjgC/rtBoclvnlCsaN7OFcZIe6ehfjwGeaw41r38Y8IgUQCkuN+IiL8FN1IiMI2f
#  kSayumwcOCAwmA4yJfu8n1v4W416jXt775YKR+1bt2Df1fNA6FnfoSMTqZl7rHn9zo76Efg
#  yvm7M0uT3uz0NZbJtqOnMFzRri9TEj4jYiCgsNaBYA9prbZlA02svoJx9qIJ2mKA+EcVpxK
#  IsEAY4ZXzXfhynKLeOYGK786ghiZrtsQYGbP6c1fAzTNy+fLJzRFozsV/wEQ==
headers = set(dkim.DKIM.SHOULD) | set(dkim.DKIM.FROZEN) | append_headers - set(dkim.DKIM.SHOULD_NOT)
signature = dkim.sign(message, selector.encode(), domain.encode(), privkey, include_headers=headers)
print(signature.decode())
# DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=mail.markjour.com;
#  i=@mail.markjour.com; q=dns/txt; s=s20190416; t=1555392275;
#  h=in-reply-to : x-smtp-id : content-type : cc : content-id : list-post
#  : date : resent-from : list-owner : list-id : to : content-description
#  : list-subscribe : message-id : sender : mime-version :
#  resent-message-id : list-help : content-transfer-encoding : resent-cc
#  : resent-date : list-unsubscribe : references : resent-sender : from :
#  list-archive : subject : resent-to : reply-to;
#  bh=qg03cTlGc4OH4uPv7BGgoUyhgh23r+o1O6qzYOLixvA=;
#  b=lLJafHJ8B/DoO4FncLp+BIHaPy4xsq7dRAWjzAvkRoDSwjcg3EloW0FsCXS45EkmwmBZC
#  Vks7zeOR1CS8oxcpauhxj1XnlwfcwLWtAQ3pogQTzNh4EEUFiNfgJTdXefAh7cpGHolQmy7
#  w2TBXDPx+Ikynw2tNnGOBduLWi+BH3Et8KGaskR4D9QHWSrk4pqeaNannNhDPUfE98d2fS3
#  kKBvqiEaTubQBdi8VXcl8J4R1SfdJZR2NfBJkPjJejlwTJaSytF2zyberpgflj0sEc8iHvM
#  2UQRcpxqm8GMRyzzKAXBSTzQhmaTOHntGokDTunNlUc/izFFRJm9SFiVq64g==

OpenDKIM

使用 opendkim-genkey 生成签名私钥和 DNS 配置文件:

$ opendkim-genkey --verbose --domain=mail.markjour.com --selector=s20190416 --directory=/tmp/
opendkim-genkey: generating private key
opendkim-genkey: private key written to s20190416.private
opendkim-genkey: extracting public key
opendkim-genkey: DNS TXT record written to s20190416.txt

域名配置好之后,可以使用 opendkim-testkey 检查:

$ opendkim-testkey -d test.markjour.com -k /tmp/private_key.pem -s s20190416 -v
opendkim-testkey: 's20190416._domainkey.test.markjour.com' record not found

#11 电子邮件的一些小技巧

2019-01-14

紧急程度 X-Priority

有另一个 Priority 头,不知道为什么没有使用。

  • 1:最高
  • 2:高级
  • 3:一般
  • 4:低级
  • 5:最低

根据网上的一些资料,这几个头已经被垃圾邮件滥用,导致可能会被拦截。
不过,企业邮箱使用应该没有问题。

网易邮箱发送的时候勾选重要,就会设置 X-Priority: 1

需要发送回执 Disposition-Notification-To

效果就是客户端会提示:“是否发送回执”,确认 or 取消。
注意,不是所有客户端会支持回执。

mdn-request-header = "Disposition-Notification-To" ":" 1#mailbox
# mailbox 就是 local-poart@domain 这种格式
# RFC 中的这个 1# 我也不知道是干嘛用的

值是正常的邮件地址格式就行了,比如:

Disposition-Notification-To: admin@example.com

Disposition-Notification-To: =?utf-8?b?566h55CG5ZGY?= admin@example.com

注意:会和 Return-Path 的域名部分做对比,如果不一致,不会发送回执。

参考:https://www.rfc-editor.org/rfc/rfc2298.html

回执参考

主要是有这么一段:

Content-Type: message/disposition-notification; name="MDNPart2.txt"
Content-Disposition: inline
Content-Transfer-Encoding: 7bit

Reporting-UA: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.7.0
Final-Recipient: rfc822;huang@example.com
Original-Message-ID: <2ec11bc0.59a.1803f61e0f9.Coremail.admin@example.com>
Disposition: manual-action/MDN-sent-manually; displayed

参考资料与拓展阅读

#10 日程邀请邮件

2019-01-10

介绍

iCal 历史

  1. Versit 联盟阶段

    1995 年,为了解决不同设备、不同程序之间交互个人数据(联系人、日程)困难的问题,苹果、AT&T、IBM 等公司组成 ​​Versit 联盟,旨在制定个人信息交换(PDI)的开放标准。
    后来,该组织设计了以下两个标准:

    • vCard,用于交换名片信息,包括姓名、地址、电话号码、邮箱地址等。
    • vCalender,用于交换日历信息,如日程、会议等。

    基本格式都是这样,只是里面的字段定义不同:

    BEGIN:type
    key1:value1
    key2:value2
    END:type
    
  2. IMC 联盟阶段

    1996 年,为了促进上面两项标准的推广,Versit 联盟将相关格式所有权移交给了互联网邮件联盟(Internet Mail Consortium,IMC)。
    PS:IMC 可能是 Versit 联盟成员拉拢更多公司组成的一个更大的联盟。
    PS:2002 年,IMC 组织关门。

    IMC closed down in 2002.

  3. IETF 阶段

    1998 年,IMC 将两个标准提交给 Internet Engineering Task Force (IETF) 标准化。
    PS:vCalendar 经过 IETF 标准化之后改名 iCalendar 了。

支持情况

经过测试,至少 QQ 和 网易邮箱的 WebMail 都提供了日程的支持。
Thunderbird 只能通过日历 App 打开附件 (ics 文件) 的方式添加日程。

效果

event mail

原理

在邮件中插入了一个 text/calendar 类型的附件:

Content-Type: text/calendar; charset=utf-8; method=REQUEST;
    name=ATT1547083200618.ics
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename="ATT1547083200618.ics"

解析出来这样一个 iCal 文件:

BEGIN:VCALENDAR
PRODID:-//Netease//WebMail
VERSION:2.0
METHOD:REQUEST
BEGIN:VEVENT
SUMMARY:日程:上午十点的面试
LOCATION:小会议室
DTSTART:20190110T013000Z
DTEND:20190110T023000Z
UID:11c27eb7-adab-4549-8aea-efadcec7bb6c
SEQUENCE:0
STATUS:CONFIRMED
ORGANIZER;CN=张三:mailto:zhangsan@example.com
ATTENDEE:mailto:lisi@example.com
END:VEVENT
END:VCALENDAR

回复

这个回复就各异了,没有同意的格式。
QQ 邮件只会有一个 自动回复: xxxx,不知道是接受还是拒绝。
QQ 企业邮件会有详细的信息,附带了原日程,并有一句话:xxx 已经接受你的邀请: 日程:xxx

参考资料与拓展阅读

#9 电子邮件是如何传输的?

2018-07-22

假设:

  1. A 要发一封邮件给 B、C。
  2. A 的地址是 aaa@163.com,B 的邮箱地址是 bbb@163.com,C 的邮箱地址是 ccc@qq.com

那么:

  1. A 先在自己的客户端 Thunderbird 上(WebMail 后面是怎么处理的就取决于各个邮件服务提供商了)将邮件编辑并发送出去。
  2. 邮件经过 SMTP(需要验证身份),到了网易的邮件发送服务 smtp.163.com
  3. 网易邮件发送服务检查了两个收信地址,发现:
    B 地址所在域和自己一致,就直接投给了自己的用户邮箱
    C 地址所在域和自己不一致,就检查 C 域的 MX 记录,得到 mx.qq.com,然后将邮件投递过去(SMTP)。
  4. B 通过客户端 Foxmail,从配置的 pop3.163.com 下载了邮件(POP3),手机自带的邮件客户端配的 imap.163.com,那就走 IMAP 协议。
  5. QQ 的 mx 服务器是没有身份验证的,但是会通过一系列手段验证 163 的连接是可靠的,才会接收这封邮件。
  6. QQ 收到邮件之后,将邮件投进 C 的收件箱。
  7. C 则在 Web 上浏览邮件,这时候 WebMail 通过某种技术,从 QQ 邮箱服务拉取到了邮件列表,C 就发现这封邮件,然后点开看到了 WebMail 获取到的邮件内容。
  QQ 163  
SMTP smtp.qq.com 465/587 smtp.163.com 25, 465/587
POP3 pop.qq.com 995 pop.163.com 110, 995
IMAP imap.qq.com 993 imap.163.com 143, 993

玩概念

可以看到各种 MxA:

  • MUA: user 用户代理,也就是邮件客户端
  • WebMail 或者 Foxmail、Thunderbird 这样的工具。
  • MSA: submission 发送代理
  • 比如 smtp.163.com 这样的收信服务
  • MTA: transfer 传送代理,或者说交换代理,又叫 Mail Exchanger 或 MX Host
  • 接收别的邮件服务转过来的邮件
  • 和 MSA 的区别是没有身份认证,但是有对投递者(服务器)身份信息的检验
  • Postfix 服务器
  • MDA: delivery 投递代理
  • 主要是对 MSA/MTA 收到的邮件进行过滤处理:
    • 检查和过滤垃圾邮件、病毒邮件等,
    • 然后外部地址的邮件就交换出去,
    • 内部地址的邮件就存储起来
  • MRA: retrieval,receive 接收/检索代理
  • POP,IMAP 协议
  • Dovecot 服务器

#8 MIME 编码

2018-05-19

说明

多用途互联网邮件扩展
Multipurpose Internet Mail Extensions

最初的电子邮件标准 RFC 822 只支持发送 ASCII 字符文本内容,通过 MIME 这个拓展(RFC 2822),可以发送所有类型的内容。
后面的 HTTP 协议也是在 MIME 这个框架内构建的,Web 开发者比较熟悉的那几个 Content-xxx 头就是从 MIME 里面来的。

规范文件

RFC No. Type Title
RFC 2045 Standards Track Multipurpose Internet Mail Extensions (MIME) Part One:
Format of Internet Message Bodies
RFC 2046 Standards Track Multipurpose Internet Mail Extensions (MIME) Part Two:
Media Types
RFC 2047 Standards Track MIME (Multipurpose Internet Mail Extensions) Part Three:
Message Header Extensions for Non-ASCII Text
RFC 2048 Best Current Practice Multipurpose Internet Mail Extensions (MIME) Part Four:
Registration Procedures
RFC 2049 Standards Track Multipurpose Internet Mail Extensions (MIME) Part Five:
Conformance Criteria and Examples

语法

MIME-Version: 1.0                           // MIME 版本
Content-Type: [type]/[subtype]; parameter   // 内容类型
Content-Transfer-Encoding: [encoding]       // 内容传输编码
Content-Disposition: [disposition]          // 内容配置

内容类型 Content-Type

又叫互联网媒体类型(Internet media type)或者 MIME 类型(MIME type)。
类型信息的注册事宜,由 IANA(Internet Assigned Numbers Authority)统一管理。

按照注册来源分成几种类型,只用关心标准数,这个了解一下就行了:

  • 标准树 类型名 / 子类型名 [ + 后缀 ] [ ; 可选参数 ]
  • 厂商树 类型名 / vnd.子类型名 [ + 后缀 ] [ ; 可选参数 ],例如:application/vnd.debian.binary-package
  • 个人树 类型名 / prs.子类型名 [ + 后缀 ] [ ; 可选参数 ]
  • 未注册的 x.树 类型名 / x.子类型名 [ + 后缀 ] [ ; 可选参数 ]

常见的类型

详细类型在 IANA 官网有,链接在下面参考资料部分我贴了一个。

Type Subtype Description
text plain 文本
text html HTML
text xml XML
text javascript JavaScript
text css CSS
text csv CSV
text vcard vCard 电子名片
image bmp
image jpeg
image png
image gif
image webp
image svg+xml
image icon
audio mpeg
audio mp4
audio ogg
audio webm
audio flac
video mpeg
video mp4
video ogg
video webm
application xml XML
application json JSON
application ecmascript
application javascript
application zip
application gzip
application pdf
application rss+xml
application atom+xml
application octet-stream

内容传输编码

编码类型主要是这几种:“7bit”,“8bit”,“binary”,“quoted-printable”,“base64”。