发送电子邮件比你想象的要复杂得多,直接从云,如Windows Azure中发送邮件的挑战更大,因为你没有一个专用IP地址,并且垃圾邮件发送者可以使用Windows Azure发送大量的垃圾邮件,如果那样,垃圾邮件黑名单很快就会将Windows Azure数据中心的IP地址范围列为垃圾邮件源,这意味着以后合法的邮件也将被列为垃圾邮件。
解决这个挑战最好的办法是禁止直接使用Windows Azure发送邮件,所有邮件全部使用第三方SMTP服务发送(如 SendGrid 或 AuthSMTP),它们一般都有自己的反垃圾邮件规则和独立的IP地址。
注意接收邮件与发送邮件完全不是一回事,只要你在Windows Azure中监听了SMTP 25端口,你就会接收到人家发给你的邮件。
使用第三方服务发送邮件
首先我在 SendGrid 免费注册了一个账号,免费账号每天可以发送200封邮件,但许多高级功能不能使用,如果你打算成为付费用户,我建议你购买银级版本。
图 1 SendGrid 邮件服务的五种版本
快速注册后,需要完善一下账号和相关配置信息。
图 2 账号信息
实际上使用 System.Net.Mail 命名空间发送邮件是相当简单的,下面就是发送邮件的代码:
var reply = new MailMessage(RoleEnvironment.GetConfigurationSettingValue(“EmailAddress”),
msg.FromAddress)
{
Subject = msg.Subject.StartsWith(“RE:”, StringComparison.InvariantCultureIgnoreCase)
? msg.Subject : string.Format(“RE: ” + msg.Subject),
Body = body,
IsBodyHtml = msg.HasHtmlBody // send HTML if we got HTML
};
if (!reply.IsBodyHtml) reply.BodyEncoding = Encoding.UTF8;
// make it a proper reply
reply.Headers[“References”] = msg.MessageID;
reply.Headers[“In-Reply-To”] = msg.MessageID;
// use our SMTP server, port, username, and password to send the mail
(new SmtpClient(RoleEnvironment.GetConfigurationSettingValue(“SmtpServer”),
int.Parse(RoleEnvironment.GetConfigurationSettingValue(“SmtpPort”)))
{
Credentials = new NetworkCredential(RoleEnvironment.GetConfigurationSettingValue(“SmtpUsername”),
RoleEnvironment.GetConfigurationSettingValue(“SmtpPassword”))
}).Send(reply);
是的,就是这么简单。因为我使用的是 SendGrid 的免费服务,每天最多只能发送200封邮件,并且没有独立IP地址,因为我发送出去的邮件很可能不能通过垃圾邮件过滤器。
使用Worker角色接收邮件
在C#下很难找到一个好用且免费的接收邮件的开发库,我使用了 Eric Daugherty 的 C# Email Server (CSES)接收邮件,并添加了 SharpMime 工具来处理解码有附件的MIME邮件,接收邮件的代码最主要要完成两件事:
1、在 OnStart() 中启动一个 TcpListener 监听合适的端口。
2、在 Run() 中启动一个循环,处理每个入站的邮件,即将邮件保存到Blob存储并进行回复。
下面是我们最初的SMTP处理程序(CSES的一部分)和在正确端口上启动 TcpListener 的代码(来自 OnStart() 的调用):
listener = new TcpListener(IPAddress.Any,
RoleEnvironment.CurrentRoleInstance.InstanceEndpoints[“SmtpIn”].IPEndpoint.Port);
processor = new SMTPProcessor(RoleEnvironment.GetConfigurationSettingValue(“DomainName”),
new RecipientFilter(), new MessageSpool());
listener.Start();
注意我使用了运行时API确定正确的端口。
下面是简单的入站TCP连接异步处理代码(来自run()的调用):
var mutex = new ManualResetEvent(false);
while (true)
{
mutex.Reset();
listener.BeginAcceptSocket((ar) =>
{
mutex.Set();
processor.ProcessConnection(listener.EndAcceptSocket(ar));
}, null);
mutex.WaitOne();
}
下面是处理入站邮件的代码:
// make a container, with public access to blobs
var id = Guid.NewGuid().ToString().Replace(“-“, null);
var container = account.CreateCloudBlobClient().GetContainerReference(id);
container.Create();
container.SetPermissions(new BlobContainerPermissions() { PublicAccess=BlobContainerPublicAccessType.Blob });
// parse the message
var msg = new SharpMessage(new MemoryStream(Encoding.ASCII.GetBytes(message.Data)),
SharpDecodeOptions.AllowAttachments | SharpDecodeOptions.AllowHtml | SharpDecodeOptions.DecodeTnef);
// create a permalink-style name for the blob
var permalink = Regex.Replace(Regex.Replace(msg.Subject.ToLower(), @”[^a-z0-9]”, “-“), “–+”, “-“).Trim(‘-‘);
if (string.IsNullOrEmpty(permalink))
{
// in case there’s no subject
permalink = “message”;
}
var bodyBlob = container.GetBlobReference(permalink);
// set the CDN to cache the object for 2 hours
bodyBlob.Properties.CacheControl = “max-age=7200”;
// replaces references to attachments with the URL of where we’ll put them
msg.SetUrlBase(Utility.GetCdnUrlForUri(bodyBlob.Uri) + “/[Name]”);
// save each attachment in a blob, setting the appropriate content type
foreach (SharpAttachment attachment in msg.Attachments)
{
var blob = container.GetBlobReference(permalink + “/” + attachment.Name);
blob.Properties.ContentType = attachment.MimeTopLevelMediaType + “/” + attachment.MimeMediaSubType;
blob.Properties.CacheControl = “max-age=7200”;
attachment.Stream.Position = 0;
blob.UploadFromStream(attachment.Stream);
}
// add the footer and save the body to the blob
SaveBody(msg, bodyBlob, message, container, permalink);
自定义域和CDN
如果你想使用邮件验证机制,如 SendGrid 的白签( whitelabel )功能(包括域名密钥),必须为你的应用程序使用自定义域名,这是因为这些功能需要你能为你的域修改DNS,我决定不使用这些功能,但我仍然决定要使用一个自定义域名 content.emailtheinternet.com 。
此外,Windows Azure CDN也是一项非常棒的服务,通过它可以让应用程序访问提速,我决定开启这项服务。
第1步:给 EmailTheInternet.com 创建CNAME记录
使用Windows Azure应用程序创建自定义域名非常简单,只需要去域名注册商控制面板创建一个从 emailtheinternet.com 到www. emailtheinternet.com 的域名转发即可。
图 3 设置域名转发
然后我再增加一个CNAME记录, 将“www”映射到 emailtheinternet.cloudapp.net (我从Windows Azure获得域名)。
图 4 将“www”映射到 emailtheinternet.cloudapp.net
最后,我想让邮件正确地路由,需要再增加一个MX记录,也映射到 emailtheinternet.cloudapp.net 。
图 5 增加MX记录
第2步:为存储账号开启CDN
为存储账号添加一个CDN端点就如点击一个按钮般简单,点击“启用CDN”按钮,就获得一个常规CDN端点( az2919.vo.smecnd.net )。
图 6 启用CDN
第3步:为CDN端点增加自定义域名
为存储账号或CDN端点增加一个自定义域名的过程是相同的,首先点击“管理”,进入CDN端点,输入自定义域名。
图 7 输入自定义域名
然后点击“生成密钥”按钮,这是我获得自定义域名验证的第一步。
图 8 生成的密钥信息
根据上图给出的指示,我增加了一条CNAME记录, 将生成的域名映射到 verify.azure.com 。
图 9 将生成的域名映射到 verify.azure.com
然后我返回门户,想通过点击来验证域名是否生效,下图显示了生效的自定义域名。
图 10 生效的自定义域名
自定义域名生效后,我再次回到域名注册商的控制面板, 将“ content ”CNAME映射到CDN端点 。
图 11 将“ content ”CNAME映射到CDN端点
你可能在上面的代码中发现我调用了一个叫做 Utility.GetCdnUrlForUri() 的方法,这个方法把Blob URI转换成使用CDN主机,我将其当作一个配置设置存储起来了。
public static string GetCdnUrlForUri(Uri uri)
{
var builder = new UriBuilder(uri);
builder.Host = RoleEnvironment.GetConfigurationSettingValue(“CdnHostName”);
return builder.Uri.AbsoluteUri;
}
我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。
我原创,你原创,我们的内容世界才会更加精彩!
【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】
微信公众号
TechTarget
官方微博
TechTarget中国