使用 C# SDK 时 Amazon SES、附件、点填充的双周期问题
Double period issue with Amazon SES, attachments, dot-stuffing when using C# SDK
任何使用过它的人都知道,要通过 Amazon SES C# SDK 发送带有附件的电子邮件,您必须使用 SendRawEmail
函数(非常臭)。为此,您要么手动编码 MIME 消息,要么将 System.Net.Mail.MailMessage
对象转换为 MemoryStream
。这一切都很好,但我遇到了一个问题。经过大量挖掘,我已经能够找到问题并复制它,它似乎是 SMTP 点填充的副产品。
问题是,在某些情况下,当 MailMessage
转换为原始 MIME 邮件时,如果邮件正文中的句点换行到原始邮件中一行的开头,则它是点填充(我认为是正确的)。但是,它似乎没有在 SDK 内部或 SES 端处理,因为电子邮件是通过双周期发送的。这可以通过以下示例控制台应用程序代码复制...
static void Main(string[] args)
{
var to = new List<string> { _toAddress };
var subject = "TEST MESSAGE";
var message = $"This is a carefully crafted HTML email message body such that you should see a single period right here ->. However, you'll see <strong>two periods</strong> in the email instead of one period like originally given in the code.";
var body = $"<br />Hello,<br /><br />{message}<br /><br />Sincerely,<br /><br />Your Tester";
var result = SendEmail(to, subject, body, null, isHtml: true);
Console.WriteLine(result ? "Successfully sent message" : "Failed to send message");
if (Debugger.IsAttached)
{
Console.WriteLine("Press any key to continue...");
Console.ReadLine();
}
}
private static bool SendEmail(List<string> to, string subject, string body, List<string> attachmentFilePaths = null, bool isHtml = false)
{
var message = new MailMessage
{
From = new MailAddress(_fromAddress),
Subject = subject,
Body = body,
IsBodyHtml = isHtml
};
foreach (var address in to)
{
message.To.Add(address.Trim());
}
if (attachmentFilePaths?.Any() == true)
{
foreach (var filePath in attachmentFilePaths)
{
message.Attachments.Add(new Attachment(filePath));
}
}
try
{
var creds = new BasicAWSCredentials(_SESAccessKey, _SESSecretKey);
using (var client = new AmazonSimpleEmailServiceClient(creds, RegionEndpoint.USEast1))
{
var request = new SendRawEmailRequest { RawMessage = new RawMessage { Data = ConvertMailMessageToMemoryStream(message) } };
Console.WriteLine($"RawMessage.Data...\r\n\r\n{Encoding.ASCII.GetString(request.RawMessage.Data.ToArray())}");
client.SendRawEmail(request);
return true;
}
}
catch (Exception ex)
{
Console.WriteLine($"AmazonSESHelper.SendEmail => Exception: {ex.Message}");
}
return false;
}
// Have to do this reflection crap in order to send attachments to the SES API
// From
private static MemoryStream ConvertMailMessageToMemoryStream(MailMessage message)
{
var stream = new MemoryStream();
var assembly = typeof(SmtpClient).Assembly;
var mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
var mailWriterConstructor = mailWriterType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(Stream) }, null);
var mailWriter = mailWriterConstructor.Invoke(new object[] { stream });
var sendMethod = typeof(MailMessage).GetMethod("Send", BindingFlags.Instance | BindingFlags.NonPublic);
sendMethod.Invoke(message, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { mailWriter, true, true }, null);
var closeMethod = mailWriter.GetType().GetMethod("Close", BindingFlags.Instance | BindingFlags.NonPublic);
closeMethod.Invoke(mailWriter, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { }, null);
return stream;
}
输出的原始 MIME 消息是...
X-Sender: noreply@bar.com
X-Receiver: foo@bar.com
MIME-Version: 1.0
From: noreply@bar.com
To: foo@bar.com
Date: 2 Dec 2016 11:45:36 -0500
Subject: TEST MESSAGE
Content-Type: text/html; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
<br />Hello,<br /><br />This is a carefully crafted HTML email me=
ssage body such that you should see a single period right here ->=
.. However, you'll see <strong>two periods</strong> in the email i=
nstead of one period like originally given in the code.<br /><br =
/>Sincerely,<br /><br />Your Tester
您可以看到点填充(同样,假设每个 SMTP 都是正确的),但在发送后它并没有被取消,正如我收到电子邮件时所看到的...
如果我添加@jstedfast MimeKit(这很棒),我可以让它工作,但在我们所有的应用程序中添加另一个依赖项,这不是我最喜欢做的事情。首先,我想把它扔到这里看看我是否遗漏了什么。如果没有,很高兴看到 SES 承认这是一个问题并解决它。否则,我必须在另一个库依赖项或将 SES 转移到另一个邮件提供商之间做出决定。
事实证明这是 AWS 方面的问题,但他们不会很快(甚至根本不会)解决。我向 GitHub 仓库发布了一个问题,如下所示:
https://github.com/aws/aws-sdk-net/issues/501
我已将 MimeKit 添加到我们的应用程序中,以取代通过反射转换为 mime。新的 ConvertMailMessageToMemoryStream
是:
private static MemoryStream ConvertMailMessageToMemoryStream(MailMessage message)
{
var stream = new MemoryStream();
var mimeMessage = MimeMessage.CreateFromMailMessage(message);
mimeMessage.Prepare(EncodingConstraint.None);
mimeMessage.WriteTo(stream);
return stream;
}
我遇到了同样的问题,但能够通过在作为 RawEmail 发送之前将 MailMessage 正文编码设置为 UTF-8 来修复它
mail.BodyEncoding = System.Text.Encoding.UTF8;
(见System.Net.Mail creating invalid emails and eml files? Inserting extra dots in host names)
任何使用过它的人都知道,要通过 Amazon SES C# SDK 发送带有附件的电子邮件,您必须使用 SendRawEmail
函数(非常臭)。为此,您要么手动编码 MIME 消息,要么将 System.Net.Mail.MailMessage
对象转换为 MemoryStream
。这一切都很好,但我遇到了一个问题。经过大量挖掘,我已经能够找到问题并复制它,它似乎是 SMTP 点填充的副产品。
问题是,在某些情况下,当 MailMessage
转换为原始 MIME 邮件时,如果邮件正文中的句点换行到原始邮件中一行的开头,则它是点填充(我认为是正确的)。但是,它似乎没有在 SDK 内部或 SES 端处理,因为电子邮件是通过双周期发送的。这可以通过以下示例控制台应用程序代码复制...
static void Main(string[] args)
{
var to = new List<string> { _toAddress };
var subject = "TEST MESSAGE";
var message = $"This is a carefully crafted HTML email message body such that you should see a single period right here ->. However, you'll see <strong>two periods</strong> in the email instead of one period like originally given in the code.";
var body = $"<br />Hello,<br /><br />{message}<br /><br />Sincerely,<br /><br />Your Tester";
var result = SendEmail(to, subject, body, null, isHtml: true);
Console.WriteLine(result ? "Successfully sent message" : "Failed to send message");
if (Debugger.IsAttached)
{
Console.WriteLine("Press any key to continue...");
Console.ReadLine();
}
}
private static bool SendEmail(List<string> to, string subject, string body, List<string> attachmentFilePaths = null, bool isHtml = false)
{
var message = new MailMessage
{
From = new MailAddress(_fromAddress),
Subject = subject,
Body = body,
IsBodyHtml = isHtml
};
foreach (var address in to)
{
message.To.Add(address.Trim());
}
if (attachmentFilePaths?.Any() == true)
{
foreach (var filePath in attachmentFilePaths)
{
message.Attachments.Add(new Attachment(filePath));
}
}
try
{
var creds = new BasicAWSCredentials(_SESAccessKey, _SESSecretKey);
using (var client = new AmazonSimpleEmailServiceClient(creds, RegionEndpoint.USEast1))
{
var request = new SendRawEmailRequest { RawMessage = new RawMessage { Data = ConvertMailMessageToMemoryStream(message) } };
Console.WriteLine($"RawMessage.Data...\r\n\r\n{Encoding.ASCII.GetString(request.RawMessage.Data.ToArray())}");
client.SendRawEmail(request);
return true;
}
}
catch (Exception ex)
{
Console.WriteLine($"AmazonSESHelper.SendEmail => Exception: {ex.Message}");
}
return false;
}
// Have to do this reflection crap in order to send attachments to the SES API
// From
private static MemoryStream ConvertMailMessageToMemoryStream(MailMessage message)
{
var stream = new MemoryStream();
var assembly = typeof(SmtpClient).Assembly;
var mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
var mailWriterConstructor = mailWriterType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(Stream) }, null);
var mailWriter = mailWriterConstructor.Invoke(new object[] { stream });
var sendMethod = typeof(MailMessage).GetMethod("Send", BindingFlags.Instance | BindingFlags.NonPublic);
sendMethod.Invoke(message, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { mailWriter, true, true }, null);
var closeMethod = mailWriter.GetType().GetMethod("Close", BindingFlags.Instance | BindingFlags.NonPublic);
closeMethod.Invoke(mailWriter, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { }, null);
return stream;
}
输出的原始 MIME 消息是...
X-Sender: noreply@bar.com
X-Receiver: foo@bar.com
MIME-Version: 1.0
From: noreply@bar.com
To: foo@bar.com
Date: 2 Dec 2016 11:45:36 -0500
Subject: TEST MESSAGE
Content-Type: text/html; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
<br />Hello,<br /><br />This is a carefully crafted HTML email me=
ssage body such that you should see a single period right here ->=
.. However, you'll see <strong>two periods</strong> in the email i=
nstead of one period like originally given in the code.<br /><br =
/>Sincerely,<br /><br />Your Tester
您可以看到点填充(同样,假设每个 SMTP 都是正确的),但在发送后它并没有被取消,正如我收到电子邮件时所看到的...
如果我添加@jstedfast MimeKit(这很棒),我可以让它工作,但在我们所有的应用程序中添加另一个依赖项,这不是我最喜欢做的事情。首先,我想把它扔到这里看看我是否遗漏了什么。如果没有,很高兴看到 SES 承认这是一个问题并解决它。否则,我必须在另一个库依赖项或将 SES 转移到另一个邮件提供商之间做出决定。
事实证明这是 AWS 方面的问题,但他们不会很快(甚至根本不会)解决。我向 GitHub 仓库发布了一个问题,如下所示:
https://github.com/aws/aws-sdk-net/issues/501
我已将 MimeKit 添加到我们的应用程序中,以取代通过反射转换为 mime。新的 ConvertMailMessageToMemoryStream
是:
private static MemoryStream ConvertMailMessageToMemoryStream(MailMessage message)
{
var stream = new MemoryStream();
var mimeMessage = MimeMessage.CreateFromMailMessage(message);
mimeMessage.Prepare(EncodingConstraint.None);
mimeMessage.WriteTo(stream);
return stream;
}
我遇到了同样的问题,但能够通过在作为 RawEmail 发送之前将 MailMessage 正文编码设置为 UTF-8 来修复它
mail.BodyEncoding = System.Text.Encoding.UTF8;
(见System.Net.Mail creating invalid emails and eml files? Inserting extra dots in host names)