实战:在Windows Azure中处理邮件

日期: 2010-06-12 作者:heyangdlut 来源:TechTarget中国 英文

  发送电子邮件比你想象的要复杂得多,直接从云,如Windows Azure中发送邮件的挑战更大,因为你没有一个专用IP地址,并且垃圾邮件发送者可以使用Windows Azure发送大量的垃圾邮件,如果那样,垃圾邮件黑名单很快就会将Windows Azure数据中心的IP地址范围列为垃圾邮件源,这意味着以后合法的邮件也将被列为垃圾邮件。

  解决这个挑战最好的办法是禁止直接使用Windows Azure发送邮件,所有邮件全部使用第三方SMTP服务发送(如 SendGrid 或 AuthSMTP),它们一般都有自己的反垃圾邮件规则和独立的IP地址。

  注意接收邮件与发送邮件完全不是一回事,只要你在Windows Azure中监听了SMTP 25端口,你就会接收到人家发给你的邮件。

  使用第三方服务发送邮件

  首先我在 SendGrid 免费注册了一个账号,免费账号每天可以发送200封邮件,但许多高级功能不能使用,如果你打算成为付费用户,我建议你购买银级版本。  

Azure 

图 1 SendGrid 邮件服务的五种版本

  快速注册后,需要完善一下账号和相关配置信息。  

Azure  

图 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 的域名转发即可。

Azure 

图 3 设置域名转发

  然后我再增加一个CNAME记录, 将“www”映射到 emailtheinternet.cloudapp.net (我从Windows Azure获得域名)。

Azure 

图 4 将“www”映射到 emailtheinternet.cloudapp.net

  最后,我想让邮件正确地路由,需要再增加一个MX记录,也映射到 emailtheinternet.cloudapp.net 。

Azure

图 5 增加MX记录

  第2步:为存储账号开启CDN

  为存储账号添加一个CDN端点就如点击一个按钮般简单,点击“启用CDN”按钮,就获得一个常规CDN端点( az2919.vo.smecnd.net )。

Azure 

图 6 启用CDN

  第3步:为CDN端点增加自定义域名

  为存储账号或CDN端点增加一个自定义域名的过程是相同的,首先点击“管理”,进入CDN端点,输入自定义域名。

Azure

图 7 输入自定义域名

  然后点击“生成密钥”按钮,这是我获得自定义域名验证的第一步。

Azure

图 8 生成的密钥信息

  根据上图给出的指示,我增加了一条CNAME记录, 将生成的域名映射到 verify.azure.com 。

Azure 

图 9 将生成的域名映射到 verify.azure.com

  然后我返回门户,想通过点击来验证域名是否生效,下图显示了生效的自定义域名。

Azure 

图 10 生效的自定义域名

  自定义域名生效后,我再次回到域名注册商的控制面板, 将“ content ”CNAME映射到CDN端点 。

Azure 

图 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

官方微博

TechTarget中国官方微博二维码

TechTarget中国