在 Windows Azure 上寄發電子郵件


Windows Azure 自 2010 年 2 月正式向全球提供服務後,在世界各地已經有許多的公司或組織將應用程式往 Windows Azure 上移轉,Microsoft 自己的 IT 部門也宣布要將全球各地分公司所使用的內部系統往 Windows Azure 上移轉,也同時宣告微軟會全力發展雲端應用的決心與成果。Email 服務已是目前大多數網站系統的必備品,例如利用 Emai l服務寄發註冊通知,會員訊息等等。但 Windows Azure 平台上目前並沒有內建 SMTP Server,因此無法直接由 Windows Azure Computes Instance 來發信,本文將會說明原委與替代方案。

Windows Azure VM 無法內建 SMTP Server 的理由

Windows Azure 的運算執行個體的虛擬機器內,包含了完整的作業系統與應用程式的執行環境,像 Windows Server 2008 64 位元作業系統,以及 IIS 7(Web Role)或工作代理行程(Worker Agent Process)等,可以確保 Windows Azure Computes 上的雲端應用程式正常執行,不過以往都會隨著 IIS 7 安裝的 SMTP Service 這個重要的寄信服務,在 Compute Instance VM 內並沒有提供,也無法由用戶客制新增(至少下筆時還沒有),所以目前並沒有能夠直接從 Windows Azure 平台上發送 Email 的作法,這個問題也是經常被評估 Windows Azure 平台時很常被提出的問題,其實微軟大可把 SMTP Services 放到 VM 中,但是這樣會有幾個大問題:

  • 依照 Windows Azure 的內部機制(Fabric Controller),VM 只開放 HTTP Port 80 和 443,而 SMTP 需要的 Port 25 沒有開放。
  • 只有 Web Role 可以安裝 SMTP Services,但這樣可能會吃到應用程式執行的資源。
  • SMTP Services 如果組態不當被人家用來做轉信站,對資料中心的頻寬也有很大的影響。

因此,如果要在 Windows Azure Computes 上發送 Email,目前只有三種方法:

  1. 透過第三方Email供應商寄信,如 SendGrid.com,AuthSMTP.com 這些供應商,Gmail 也可以,但不適用於商業用 SMTP,若是要用在商業運轉的話,建議還是選用商業用的 Email 服務,如此至少可以確保寄信的品質。這個方法的優點是成本低,不需特別安排管理人員等,但缺點就是無法完全控制伺服器的環境。
  2. 在企業內建置 Email 伺服器(例如 Microsoft Exchange Server 或 Exchange Online Service),並且由雲端呼叫 Email 伺服器的服務,優點是可以完全控制伺服器,並應企業需求來建置,可確保像安全需求或是擴充需求等,但缺點是成本較高且要有專業的管理人員來管理Email伺服器。
  3. 在企業內建置 Email 伺服器,但是由 Email 伺服器本身(或另外撰寫程式)向 Windows Azure要求資料,應用程式可以將送信要求保存在 Queue(指令)和 BLOB(內容與附件),而 Email 伺服器會像 Windows Azure Queue 和 BLOB 查詢是否有待送資料,如果有的話就將資料抓取下來交由本地的 SMTP Server 寄送,優缺點同由雲端呼叫 Email 伺服器的服務。

寄發電子郵件的架構考量

視應用程式或企業流程架構的不同,可能會有不同的 Email 方式與作法,透過外部 SMTP Service 的方式適合小型應用程式或是企業本身沒有架設 SMTP Server 的情境。若企業自己有架設 SMTP Server(尤其是 Exchange Server)時,則可以考慮將發信的工作交由自己的 SMTP Server 來執行,或是撰寫自訂的工作流程(EWS)納入 Exchange Server 的工作流程內,如同下圖的架構:

透過自行撰寫 EWS(Exchange Web Service)的架構,可以套用於本地的 Exchange Server 以及 Exchange Online Services,並發揮 SMTP Server 的能力,本文稍後會介紹這個部份。

 

NOTE

您可以參考下列文章取得自訂 EWS API 以發送信件的說明:
https://blogs.msdn.com/b/windowsazure/archive/2010/10/15/adoption-program-insights-sending-emails-from-windows-azure-part-2-of-2.aspx

 

您也可以在本地設計應用程式,向 Windows Azure Queues 或 BLOB 儲存查詢是否有郵件寄發的要求訊息,如果有的話就下載下來,再由本地的 SMTP Server 來寄發。這個架構適合企業使用 IIS 的 SMTP Services 取代 Exchange Server 或是第三方的 SMTP Server 作為寄發郵件的伺服器(如下圖):

這樣做的優點是不必花費第三方 SMTP Server 的帳戶費用,以及不用架設 Exchange Server 這類的訊息伺服器,但在應用程式的設計上的工作就會變多(包含由雲端應用程式發出郵件寄送要求、撰寫查詢郵件寄送要求以及寄發郵件的應用程式等)。

由 Windows Azure 使用外部 SMTP Server 寄信

我們以一個範例專案來示範第一種作法,由於筆者目前只有 Gmail 的帳戶可以測試,因此筆者就以 Gmail 為範例。第二種和第三種作法和第一種基本上差異很小,只差在 SMTP Server 的設定而已。

 

NOTE

Exchange Online 於今年八月份的更新,開放了對外的 SMTP Service,只要在 Exchange Online 中有登錄信箱(使用者)時,就可以使用這個方式寄信,它的作法和 Gmail 極為類似,它的相關資料為:

  • SMTP 伺服器:Smtp.mail.microsoftonline.com。
  • 帳戶與密碼:使用登錄於 Exchange Online 內的使用者的帳戶。
  • 安全機制:TLS,啟用 SSL 即可(SmtpClient.EnableSsl = true)。
  • 通訊埠號:587。

 

Step 1. 請開啟 Visual Studio 2010,並新增一個雲端應用程式的專案(筆者使用的是C#),命名為 SendingEmailFromAzureVM,然後按確定:

Step 2. 請加入一個新的 ASP.NET 應用程式專案,命名為 SendingEmailWeb:

此時,方案總管中產生的專案結構會類似如下圖:

Step 3. 請以 HTML 檢視模式開啟 Default.aspx,並且加入下列 HTML 指令碼:

[HTML]

<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent"> <asp:Literal ID="ltMessageSent" runat="server" /> <p> 正本:<asp:TextBox ID="txtMailTo" runat="server" Width="400px" /> <asp:RequiredFieldValidator ID="rfvMailTo" ControlToValidate="txtMailTo" runat="server" Display="Dynamic" ErrorMessage="請輸入信件收件人。" /><br /> 副本:<asp:TextBox ID="txtMailCC" runat="server" Width="400px" /><br /> 密副:<asp:TextBox ID="txtMailBCC" runat="server" Width="400px" /> </p> <p> 主旨:<asp:TextBox ID="txtMailSubject" runat="server" Width="400px" /><br /> </p> <p> 內文:<br /> <asp:TextBox ID="txtMailBody" runat="server" TextMode="MultiLine" Width="500px" Height="300px" /> <asp:RequiredFieldValidator ID="rfvMailBody" ControlToValidate="txtMailBody" runat="server" Display="Dynamic" ErrorMessage="請輸入信件內文。" /> </p> <p> <asp:CheckBox ID="chkIsBodyHtml" runat="server" Text="內文為HTML" /> </p> <p> <asp:Button ID="cmdSendMail" runat="server" Text=" 寄發信件 " onclick="cmdSendMail_Click" /> </p> </asp:Content>

此時切換到設計模式,網頁看起來應該是這樣:

Step 4. 請開啟 Web.config 檔案,並加入下列 <appSettings> 設定:

<appSettings> <add key="SenderMailAddress" value="[你的Gmail郵件位址]" /> <add key="SenderSmtpServer" value="smtp.gmail.com" /> <add key="SenderSmtpPort" value="587" /> <add key="SenderSmtpUserName" value="[你的 Gmail 帳戶]" /> <add key="SenderSmtpPassword" value="[你的 Gmail 密碼]" /> <add key="SenderSmtpRequiresSSL" value="true" /> </appSettings>

 

NOTE

如果您是使用您自己架設的 SMTP Server,或是使用其他可發信的 SMTP Service(例如 AuthSMTP.com)時,請依該 SMTP Server 的設定資料來設定上列的組態。

 

並且在 Web.config 的 <system.web> 區段中,加入下列指令:

<httpRuntime requestValidationMode="2.0"/> <pages validateRequest="false" />

 

NOTE

<httpRuntime requestValidationMode="2.0"/> 是 ASP.NET 4.0 才需要的,目的是允許停用 ASP.NET 的 ValidateRequest,以讓 <pages validateRequest="false" /> 的設定生效。

 

Step 5. 請開啟 Default.aspx.cs,先在命名空間加入引用 System.Net.Mail 命名空間的宣告:

[C#]

using System.Net.Mail;

然後,加入下列程式碼:

[C#]

protected void cmdSendMail_Click(object sender, EventArgs e) { // configure MailMessage msg = new MailMessage(); msg.From = new MailAddress(ConfigurationManager.AppSettings["SenderMailAddress"]); string[] receiptAddresses = this.txtMailTo.Text.Split(';'); foreach (string receiptAddress in receiptAddresses) msg.To.Add(new MailAddress(receiptAddress.Trim())); if (!string.IsNullOrEmpty(this.txtMailCC.Text)) { string[] ccAddresses = this.txtMailTo.Text.Split(';'); foreach (string ccAddress in ccAddresses) msg.CC.Add(new MailAddress(ccAddress.Trim())); } if (!string.IsNullOrEmpty(this.txtMailBCC.Text)) { string[] bccAddresses = this.txtMailTo.Text.Split(';'); foreach (string bccAddress in bccAddresses) msg.Bcc.Add(new MailAddress(bccAddress.Trim())); } msg.Subject = this.txtMailSubject.Text; msg.IsBodyHtml = this.chkIsBodyHtml.Checked; msg.Body = this.txtMailBody.Text; // configure SMTP Server. SmtpClient smtpClient = new SmtpClient(); smtpClient.Host = ConfigurationManager.AppSettings["SenderSmtpServer"]; smtpClient.Port = Convert.ToInt32(ConfigurationManager.AppSettings["SenderSmtpPort"]); smtpClient.EnableSsl = (Convert.ToBoolean(ConfigurationManager.AppSettings["SenderSmtpRequiresSSL"])) ? true : false; smtpClient.Credentials = new NetworkCredential(ConfigurationManager.AppSettings["SenderSmtpUserName"], ConfigurationManager.AppSettings["SenderSmtpPassword"]); smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network; smtpClient.Send(msg); this.ltMessageSent.Text = "<p><b>郵件已寄發。</b></p>"; smtpClient = null; msg = null; }

此程式碼是按鈕的 Click 事件處理常式,用來將使用者輸入的信件資訊填入 MailMessage 類別,並用 SmtpClient 類別將信件寄出。

Step 6. 建置應用程式,並請按 Ctrl+F5 使用 Development Fabric 來執行應用程式,此時瀏覽器應該會開啟,並載入 Web 應用程式的 Default.aspx,您可以看到這個畫面:

 

NOTE

您可以開啟 Development Fabric UI,以確定應用程式正在執行中。

 

Step 7. 請輸入測試郵件的收件人、副本與密件副本(如有多個,請用”;”來分隔,收件人為必填欄位),信件主旨與內文,內文若是 HTML,則要多勾選「內文為 HTML」的核取方塊:

然後按下「寄發信件」按鈕,稍後就會看到「郵件已寄發」的訊息:

寄發後,請開啟您的收件人的郵件帳戶(筆者是用 Outlook 2010),您應該可以看到由 Windows Azure 應用程式寄發的 Email:

Step 8. 請將應用程式部署到雲端,在部署完成後,您可以在部署完成後瀏覽在雲端上的應用程式,並且按照 Step 7 的步驟,一樣可以成功寄發郵件:

 

NOTE

在部署到雲端前,請記得將 WebRole.cs 打開,並將 DiagnosticsMonitor.Start(“…”) 這一行註解或刪除,否則會發生無窮初始化的問題。

public override bool OnStart()

{

    //DiagnosticMonitor.Start("DiagnosticsConnectionString");

    // 如需有關處理組態變更的資訊,

    // 請參閱位於 https://go.microsoft.com/fwlink/?LinkId=166357 的 MSDN 主題。

    RoleEnvironment.Changing += RoleEnvironmentChanging;

    return base.OnStart();

}

 

使用 Exchange Web Service 寄信

對於使用 Microsoft Exchange Server 或是 Exchange Online 的企業來說,如果不能使用內建的信件發送功能的話,似乎是一種資源的浪費,而且通常在企業內建置 Exchange Server 的話,不會只用它的 Mail Server 功能而已,像是用到與 Active Directory 整合,或是加密或安全的郵件通訊等 Exchange Server 才有的特異功能;但純粹的 SMTP 無法使用這些功能,這時就只能利用 Exchange Server 所開放的 API 來存取 Exchange Server,以往 Exchange Server 的 API 只有 CDO(Collaborative Data Object)以及 Exchange Server 的 COM API,但隨著 Exchange Server 的功能與適用環境的增加,讓 Exchange Server 重新建置了可以相容於網路上的服務 API,亦即 Exchange Web Services(EWS)API,這個 API 可讓應用程式存取 Exchange Server 上大部份的功能,我們也能利用它來寄發郵件。但本文不會說明如何建置與設定 Exchange Server,也不會說明如何申請 Exchange Online 服務,本文假定您已經有可用的 Exchange Server 環境或是已申請使用 Exchange Online 服務。

 

NOTE

Exchange Online 是微軟的雲端訊息服務(Cloud Messaging Services),可將它視為 Exchange Server as a Service(雲端上的 Exchange Server),提供 Exchange Server 的訊息通訊能力,在台灣目前已有 30 天試用方案,您可以利用這個方案來測試 Exchange Online 的能力。

關於 Exchange Online 服務的說明,可參考 https://www.microsoft.com/exchange/2010/en/us/exchange-online.aspx

關於Exchange Online 管理的說明,可參考

https://technet.microsoft.com/en-us/library/cc742565.aspx

 

若要開發雲端應用程式以使用 Exchange Web Service 來寄信,則必須要先滿足下列需求:

本文會使用 Visual Studio 2010 以及 Windows Azure 雲端應用程式,配合 Exchange Online 服務的 EWS API 作為範例,所需的郵件帳戶都已經預先在 Exchange Online 中設定。

我們就以前面的範例程式為主,修改它以支援 EWS API。

Step 1. 請在 SendingEmailWeb 專案內,加入對 Microsoft.Exchange.WebServices.dll 組件的參考,這個組件是 Microsoft Exchange Web Services Managed API 的一部份。

Step 2. 請在 SendingEmailWeb 專案內,加入一個新的 SendMailViaEWS.aspx 網頁(引用現有的主版頁面),然後由 Default.aspx 內複製畫面用的 HTML 到 SendMailViaEWS.aspx 內,它的畫面和 Default.aspx 是相同的。

Step 3. 請打開 Web.config,並在 <appSettings> 區段中加入針對 EWS 的設定:

<add key="EWS_SenderMailAddress" value="[寄件者Email]" /> <add key="EWS_SenderMailUserName" value="[Exchange Server或Exchange Online的登入帳戶]" /> <add key="EWS_SenderMailPassword" value="[密碼]" /> <add key="EWS_WebServiceUrl" value="[EWS API URL]" />

Step 4. 請在 SendMailViaEWS.aspx.cs 中,加入處理 cmdSendMail 按鈕的 Click 事件的處理常式:

// prepare service. ExchangeService exchangeService = new ExchangeService(ExchangeVersion.Exchange2007_SP1); exchangeService.Credentials = new WebCredentials( ConfigurationManager.AppSettings["EWS_SenderMailUserName"], ConfigurationManager.AppSettings["EWS_SenderMailPassword"]); exchangeService.Url = new Uri(ConfigurationManager.AppSettings["EWS_WebServiceUrl"]); // send mail. EmailMessage message = new EmailMessage(exchangeService); message.Sender = new EmailAddress(ConfigurationManager.AppSettings["EWS_SenderMailAddress"]); string[] receiptAddresses = this.txtMailTo.Text.Split(';'); foreach (string receiptAddress in receiptAddresses) message.ToRecipients.Add(new EmailAddress(receiptAddress.Trim())); if (!string.IsNullOrEmpty(this.txtMailCC.Text)) { string[] ccAddresses = this.txtMailTo.Text.Split(';'); foreach (string ccAddress in ccAddresses) message.CcRecipients.Add(new EmailAddress(ccAddress.Trim())); } if (!string.IsNullOrEmpty(this.txtMailBCC.Text)) { string[] bccAddresses = this.txtMailTo.Text.Split(';'); foreach (string bccAddress in bccAddresses) message.BccRecipients.Add(new EmailAddress(bccAddress.Trim())); } message.Subject = this.txtMailSubject.Text; message.Body = new MessageBody(((this.chkIsBodyHtml.Checked) ? BodyType.HTML : BodyType.Text), this.txtMailBody.Text); message.SendAndSaveCopy(); message = null; exchangeService = null; this.ltMessageSent.Text = "<p><b>郵件已寄發。</b></p>";

這段程式會透過 Exchange Web Service API 來發送信件,由於本文是以 Exchange Online 為基礎,Exchange Online 在本文下筆時仍是以 Exchange Server 2007 為主,所以在產生 ExchangeService 物件時的版本只能選擇 ExchangeVersion.Exchange2007_SP1,使用 ExchangeVersion.Exchange2010 的話會擲回『目前版本不支援此功能』的例外。

本段程式碼的另外一個重點是 EWS API 的位置,就如同我們在 Web Service 的用戶端設定的一樣,其實 Exchange Web Service Managed API 的組件本身就是依照 EWS 的 Web Service 產生的用戶端程式碼的包裝而已,因此我們不用再加入 Exchange Web Service 的參考,但我們仍然需要設定 URL 的位置:

資料中心位置 EWS API URL
北美地區 https://red001.mail.microsoftonline.com/ews/Exchange.asmx
歐洲、中東和非洲 (EMEA) https://red002.mail.emea.microsoftonline.com/ews/Exchange.asmx
亞太地區 (APAC) https://red003.mail.apac.microsoftonline.com/ews/Exchange.asmx

 

NOTE

如果應用程式分散在全球,或是在全球各地都有 Exchange Online 的服務時,則可考慮使用 Autodiscover 的功能,讓 Exchange Online 可以由給定的 Email 信箱來推斷它所在的 EWS API 位置。在 Exchange Online 上有提供如何設定 Exchange Online 使用 Autodiscover 的功能,如果您要在您企業內部的 Exchange Server 中設定 Autodiscover 的功能,則要參考 Exchange Server 的文件來設定。

若您要在程式中使用 Autodiscover 的功能,則您要呼叫 ExchangeServce.AutodiscoverUrl(),它會自動執行 Autodiscover 的程序,找到傳入的信箱對應的 EWS API 入口。

exchangeService.AutodiscoverUrl(

    ConfigurationManager.AppSettings["EWS_SenderMailAddress"],

    new AutodiscoverRedirectionUrlValidationCallback((string url) => { return true; })

);

不過如果使用 Autodiscover 的話,會在第一次執行 Autodiscover 時花費較多的時間。

 

NOTE

如果您是要由企業內的 EWS API 寄信時,除了 URL 會不同(請自行詢問管理 Exchange Server 的 MIS 人員)時,可能也會遇到憑證的問題,這個部份的解決方案,可以參考這篇文章:https://blogs.msdn.com/b/windowsazure/archive/2010/10/15/adoption-program-insights-sending-emails-from-windows-azure-part-2-of-2.aspx

 

Step 5. 編譯並使用 Development Fabric 執行此應用程式,並瀏覽到 SendMailViaEWS.aspx 網頁,使用與前面範例的 Step 7 相同的程序來測試使用 EWS 發送信件是否正常,正常的話您應該會收到來自 EWS 的信件(寄件者的寄件備份中也會出現以程式寄發的信件)。

 

NOTE

本文不深入說明各項 Exchange Web Services Managed API,如果您想要修改範例程式加入自己的功能時,請參考 Exchange Server SDK 文件以獲得 Managed API 的相關資訊:

https://msdn.microsoft.com/en-us/library/dd633710(EXCHG.80).aspx

 

結語

本文討論了在 Windows Azure 上寄發 Email 的不同方法,並以外部 SMTP Server 和 Exchange Web Service API 為範例展示了寄發 Email 的功能,您可以以這支範例程式為基礎發展您的 Email 功能。

 

範例程式下載

本文的範例程式碼可由此取得:
http://cid-266e4a8b12eeb19e.office.live.com/self.aspx/Sample%20Codes/WindowsAzureAppSendingEmail.rar