2017 年 8 月
第 32 卷,第 8 期
本文章是由機器翻譯。
Microsoft Office - Outlook 可採取動作的郵件
由Woon Kiat Kiat|2017 年 8 月
我喜歡電子郵件。工作、 已放入頂端發生什麼事,以及哪些我在哪裡尋求必須進行。它會接收 noti-fications 新等我的小組,對我推文,新的註解為我的提取要求的新回覆所提交的經費支出報表。但是,電子郵件可能是比較好。為什麼需要按一下電子郵件中的連結,然後等待財務系統網站,我可以核准經費支出報表之前,在瀏覽器中載入? 為什麼必須心理變更我的內容? 我可以核准直接在我的電子郵件用戶端的內容中的經費支出報表。
聽起來很熟悉嗎? Outlook 會讓您的生活更好,節省您的時間,並讓您更有效率。
介紹可採取動作的訊息
可採取動作的訊息,讓使用者在完成電子郵件本身內的工作。它提供原生的體驗,在 Outlook 桌面用戶端和 Outlook Web Access (OWA)。在本文中,我將使用 word Outlook 表示 Outlook 桌面用戶端或 OWA。
在範例中使用,虛構公司 Contoso 有內部的經費支出核准系統。每次員工提交經費支出報表時,電子郵件訊息傳送給管理員核准。我如何使用 Outlook,可讓核准內電子郵件訊息 it-自助要求的管理員可採取動作的訊息會逐步執行的步驟。
我的第一個可採取動作訊息
在圖 1,您會看到可採取動作的訊息的 HTML。它可能會看起來很複雜,但我認為,則不是。我將說明下列各節中詳細的標記。第一個步驟是傳送電子郵件以從標記圖 1 Office 365 電子郵件帳戶。
圖 1 HTML 的 Outlook 可採取動作訊息
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf8">
<script type="application/ld+json">{
"@context": "http://schema.org/extensions",
"@type": "MessageCard",
"hideOriginalBody": "true",
"title": "Expense report is pending your approval",
"sections": [{
"text": "Please review the expense report below.",
"facts": [{
"name": "ID",
"value": "98432019"
}, {
"name": "Amount",
"value": "83.27 USD"
}, {
"name": "Submitter",
"value": "Kathrine Joseph"
}, {
"name": "Description",
"value": "Dinner with client"
}]
}],
"potentialAction": [{
"@type": "HttpPost",
"name": "Approve",
"target": ""
}, {
"@type": "OpenUri",
"name": "View Expense",
"targets": [ { "os": "default",
"uri": "https://expense.contoso.com/view?id=98432019"} ]
}]
}
</script>
</head>
<body>
<p>Please <a href="https://expense.contoso.com/view?id=98432019">approve</a>
expense report #98432019 for $83.27.</p>
</body>
</html>
中所示圖 2,在訊息本身中沒有訊息卡片具有兩個按鈕,您可以與之互動。如果您按一下 [核准] 按鈕時,它會導致錯誤現在因為還沒指定動作的 URL。您稍後會加入 URL。如果您按一下 [檢視費用報表] 按鈕,瀏覽器會開啟,並瀏覽至經費支出核准網站。
圖 2 中 Outlook Web Access 可採取動作的訊息
MessageCard 標記
電子郵件訊息本身是典型的 HTML 標記。若要在 Outlook 中,讓它可採取動作的訊息,您可以插入訊息卡標記 < 指令碼 > 元素中。這種方法的主要優點之一是訊息將會繼續如往常般呈現不認得 MessageCard 標記的用戶端上該電子郵件。此標記的格式稱為 JSON LD,這是標準格式,若要建立透過網際網路的電腦可讀取的資料。現在,我們再逐行詳細資料中的標記。這些兩行程式碼會強制在每個標記中:
"@context": "http://schema.org/extensions",
"@type": "MessageCard",
您將內容設定為 http://schema.org/extensions 並輸入要 「 MessageCard。 」 MessageCard 類型表示此電子郵件是可採取動作的訊息。
接下來是將屬性"hideOriginalBody。 」 隱藏的值設為 true 時,電子郵件內文,並且只卡會顯示,如中所示圖 2。卡本身包含的使用者會需要的所有資訊時,這非常有用,或智慧卡的內容是多餘的電子郵件本文的內容。如果不了解訊息卡片的電子郵件用戶端中檢視訊息,然後將顯示原始的主體和訊息卡不是,將"hideOriginalBody。 」 的值為何 屬性"title"的值是 MessageCard 標題:
"hideOriginalBody": "true",
"title": "Expense report is pending your approval",
接下來是 「 區段 」。 您可以將一個區段,以表示 「 活動 」。 如果您的卡片具有多個活動,您應該明確地使用多個區段,其中每個活動。圖 3 顯示一個區段的標記。您可以使用事實的區段的屬性,也就是名稱 / 值組的陣列,以顯示 [費用報表的詳細資料。
圖 3 卡具有一個區段
"sections": [{
"text": "Please review the expense report below.",
"facts": [{
"name": "ID",
"value": "98432019"
}, {
"name": "Amount",
"value": "83.27 USD"
}, {
"name": "Submitter",
"value": "Jonathan Kiev"
}, {
"name": "Description",
"value": "Dinner with client"
}]
}],
接下來是"potentialAction。 」 這是可以在這張介面卡叫用的動作陣列。目前支援的動作而 OpenUri HttpPOST:
"potentialAction": [{
"@type": "HttpPost",
"name": "Approve",
"target": ""
}, {
"@type": "OpenUri",
"name": "View Expense",
"targets": [ { "os": "default",
"uri": "https://expense.contoso.com/view?id=98432019"} ]
}]
OpenUri 動作會開啟瀏覽器並瀏覽至目標屬性中指定的 URL。目標屬性是陣列,可讓您指定的平台特定的 Url。比方說,您可能希望使用者在 iOS 和 Android navi-門至不同的 Url。在此範例中,您可以設定作業系統預設值,這表示 URL 也適用於所有平台。
HttpPOST 動作會對外部 Web 服務的目標屬性中指定的 HTTP POST 要求。目前訊息的值是空的。這就是為什麼當您按一下 [核准] 按鈕時,您會看到錯誤。
MessageCard 遊樂場應用程式
如果您無法將視覺化卡片的您所撰寫的標記外觀,它可能會很棒。Microsoft 有 Web 應用程式,可讓您執行上述工作。它稱為 MessageCard 遊樂場應用程式 (bit.ly/2s274S9)。
您應該要設計您的應用程式中的卡第一次。一旦您滿意卡片配置,您可以使用標記以電子郵件訊息。
呼叫具有 HttpPOST 動作的外部 Web 服務
現在,您會有訊息卡與兩個動作。OpenUri 會開啟瀏覽器並瀏覽至動作中指定的 URL。HttpPOST 動作,您想要呼叫您的 REST API 將核准的經費支出報表。您以下列內容取代 HttpPOST 動作:
{
"@type": "HttpPost",
"name": "Approve",
"target": "https://api.contoso.com/expense/approve",
"body": "{ \"id\": \"98432019\" }"
}
當使用者按一下 [核准] 按鈕時,Microsoft server 提出 HTTP POST 要求,類似於資料夾 lowing:
POST api.contoso.com/expense/approve
Content-Type: application/json
{ "id": "98432019" }
目標是 Microsoft server 即將進行的 POST 要求的 URL,且主體為要求的內容。本文內容一律假設為 JSON。
現在,您要將您自己的新標記的電子郵件。當您按一下 [核准] 按鈕時,此動作為成功地完成 ed。
ActionCard 動作
現在讓我們加入拒絕按鈕,讓使用者可以拒絕經費支出報表。拒絕,您必須另外輸入的使用者說明為何會拒絕的經費支出報表。
ActionCard 動作被針對這種情況。它包含一或多個輸入和相關聯的動作,可以是 OpenUri 或 HttpPost。中所示,插入 ActionCard 動作 HttpPOST 和 OpenUri,之間圖 4。
圖 4 ActionCard 動作
"potentialAction": [{
"@type": "HttpPost",
...
}, {
"@type": "ActionCard",
"name": "Reject",
"inputs": [{
"@type": "TextInput",
"id": "comment",
"isMultiline": true,
"title": "Explain why the expense report is rejected"
}],
"actions": [{
"@type": "HttpPOST",
"name": "Reject",
"target": "https://api.contoso.com/expense/reject",
"body": "{ \"id\": \"98432019\", \"comment\": \"{{rejectComment.value}}\" }"
}]
},{
"@type": "OpenUri",
...
}]
如果您傳送自行更新的標記,有幾個核准、 拒絕及檢視費用報表的按鈕。如果您按一下 Re ject 按鈕時,您現在可以在拒絕的經費支出報表之前輸入註解。
讓我們看看 ActionCard 動作標記。以外的類型和名稱屬性,其具有輸入和動作的陣列。在此範例中,您會有多行的輸入,讓使用者輸入的文字。其他支援的輸入是 DateInput 和 MultichoiceInput。如需詳細資訊,請參閱bit.ly/2t3bLJN。
您有 HttpPOST 動作,可讓外部 Web 服務呼叫,以拒絕的經費支出報表。這是 simi-lar HttpPOST 動作核准動作。一項主要差異是您想要傳遞至 Web 服務呼叫的使用者輸入的註解。您可以參考的文字輸入使用 {{rejectComment.value}} rejectComment 所在的文字輸入的識別碼值。
Web 服務,可採取動作的訊息
到目前為止所見標記中 Outlook 和它的運作方式可採取動作的訊息。在文件的其餘部分,我將會取消說明 Web 服務應該如何處理來自可採取動作的訊息,在 Outlook 中的要求。
可採取動作的訊息將會使用任何 Web 服務可以處理 HTTP POST 要求。在此範例中,您的 Web 服務是此 API 控制器會在 ASP.NET MVC 中。圖 5顯示您的 API 控制器。
圖 5 費用 API 控制器
[RoutePrefix("expense")]
public class ExpenseController : ApiController
{
[HttpPost]
[Route("approve")]
public HttpResponseMessage Approve([FromBody]JObject jBody)
{
string expenseId = jBody["id"].ToString();
// Process and approve the expense report.
HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("CARD-ACTION-STATUS", "The expense was approved.");
return response;
}
[HttpPost]
[Route("reject")]
public HttpResponseMessage Reject([FromBody]JObject jBody)
{
string expenseId = jBody["id"].ToString();
string comment = jBody["comment"].ToString();
// Process and reject the expense report.
HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("CARD-ACTION-STATUS", "The expense was rejected.");
return response;
}
}
共有兩種方法,此 API 控制器中的,一個用於核准和拒絕的另一個。Web 服務必須傳回 HTTP 狀態碼的 2xx 動作才能算是成功。Web 服務也可以在回應中包含的卡片動作狀態標頭。此標頭的值將顯示給使用者的智慧卡的保留區。如果您部署至 Web 服務https://api.contoso.com並且您按一下 [核准] 5d; 按鈕,就會收到的操作已順利完成,如中所示 noti fication圖 6。
圖 6 經費支出報表具有核准成功通知
您現在已可採取動作的訊息使用端對端。您可以傳送出可採取動作的訊息和 HTTP POST 要求當使用者按一下 [核准] 按鈕時,對您的 Web 服務。您的 Web 服務會處理要求,並傳回 200 確定。Outlook 會接著在標記動作完成。接下來,我會探討您如何保護您的 Web 服務。
有限用途語彙基元
因為費用識別碼通常是跟特定格式,會有攻擊者可以藉由公佈的許多不同的費用 Id 與要求執行攻擊的風險。如果攻擊成功猜測費用編號,攻擊者可能能夠核准或拒絕該經費支出報表。Microsoft 建議開發人員使用 「 有限用途語彙基元 」 一部分的動作目標 URL,或在要求主體中。有限用途語彙基元應該是硬式攻擊者更難以猜測。例如,使用 GUID 做為有限用途權杖的 128 位元數字。這個語彙基元可以用來與服務 Url 使用者與特定要求相互關聯。它也可以用來防止重新執行攻擊 (bit.ly/2sBQmdn) 的 Web 服務。您更新主體中包含 GUID 的標記:
{
"@type": "HttpPost",
"name": "Approve",
"target": "https://api.contoso.com/expense/approve",
"body": "{ \"id\": \"98432019\", \"token\": \
"d8a0bf4f-ae70-4df6-b129-5999b41f4b7f\" }"
}
承載權杖
雖然有限用途語彙基元,讓攻擊者偽造要求,它們仍不完整。在理想情況下,Web 伺服器,應該能夠告訴 HTTP POST 要求是否來自 Microsoft 的伺服器,而不是某些未經授權的潛在惡意伺服器。
Microsoft 會解決此問題,它會傳送至 Web 服務的每個 HTTP POST 要求中包含的承載權杖。承載權杖是 JSON Web Token (JWT),並且包含要求的授權標頭中。當使用者按一下 [核准] 按鈕時,Web 服務會收到要求看起來像這樣:
POST https://api.contoso.com/expenses/approve
Content-Type: application/json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjhxZ3A4VERCbDJINkp5RkU0WjM0ZDJoYS1rRSIsImtpZCI6IjhxZ3A4VERCbDJINkp5RkU0WjM0ZDJoYS1rRSJ9.eyJpYXQiOjE0ODQwODkyNzksInZlciI6IlNUSS5FeHRlcm5hbEFjY2Vzc1Rva2VuLlYxIiwiYXBw aWQiOiI0OGFmMDhkYy1mNmQyLTQzNWYtYjJhNy0wNjlhYmQ5OWMwODYiLCJzdWIiOiJkYXZpZEBj b250b3NvLmNvbSIsImFwcGlkYWNyIjoiMiIsImFjciI6IjAiLCJzZW5kZXIiOiJleHBlbnNlYXBw... (truncated for brevity)
{
"id": "98432019",
"token": "d8a0bf4f-ae70-4df6-b129-5999b41f4b7f"
}
以下的授權標頭中"變更 Bearer"是長時間的 base 64 編碼的字串是 JSON Web Token (JWT)。您可以解碼在 jwt.calebb.net JWT。圖 7它解碼之後,會顯示範例權杖。
圖 7 A 範例承載 JSON Web 權杖
{
typ: "JWT",
alg: "RS256",
x5t: "8qgp8TDBl2H6JyFE4Z34d2ha-kE",
kid: "8qgp8TDBl2H6JyFE4Z34d2ha-kE"
}.
{
iat: 1484089279,
ver: "STI.ExternalAccessToken.V1",
appid: "48af08dc-f6d2-435f-b2a7-069abd99c086",
sub: "david@contoso.com",
appidacr: "2",
acr: "0",
sender: "expenseapproval@contoso.com",
iss: "https://substrate.office.com/sts/",
aud: "https://api.contoso.com",
exp: 1484090179,
nbf: 1484089279
}.
[signature]
每個 JWT 具有三個區段,以點 (.) 分隔。第一個區段是描述套用至 JWT 的 crypto 圖形作業的標頭。在此情況下,用來簽署權杖的演算法 (alg) 是 RS256,這表示使用 sha-256 雜湊演算法的 RSA。X5t 值會指定用來簽署權杖的金鑰的憑證指紋。
第二個區段是本身的內容。它有一份語彙基元判斷提示的宣告。Web 服務應該使用這些宣告來驗證要求。中的資料表圖 8說明這些宣告。
宣告在承載中的圖 8 說明
宣告 | 描述 |
iss | 權杖簽發者。值應該一律是 https://substrate.office.om/sts/。如果值不符合,Web 服務應該拒絕語彙基元和要求。 |
appid | 發行權杖的應用程式識別碼。值應該一律是48af08dc-f6d2-435f-b2a7-069abd99c086。如果值不符合,Web 服務應該拒絕語彙基元和要求。 |
則 | 語彙基元的對象。其值必須符合 Web 服務 URL 的主機名稱。如果值不符合,Web 服務應該拒絕語彙基元和要求。 |
sub | 主旨人員執行此動作。如果電子郵件地址或任何 proxy 電子郵件地址中,值會是執行動作之人員的電子郵件地址: 列。如果沒有任何電子郵件地址配對時,這會是主體的使用者主體名稱 (UPN) 的雜湊的值。它具有保證能夠針對相同的 UPN 相同的雜湊的值。 |
寄件者 | 原始訊息寄件者電子郵件地址。 |
tid | 權杖簽發者的租用戶識別碼。 |
第三個區段是語彙基元的數位簽章。藉由驗證簽章,Web 服務可以確信權杖會傳送 microsoft 和信任權杖中的宣告。
驗證數位簽章是一個複雜的工作。幸運的是,沒有文件庫,以簡化驗證工作的 NuGet。媒體櫃將會位於bit.ly/2stq90c ,它 Microsoft 所設計。Microsoft 也會發行有關如何驗證權杖的其他語言的程式碼範例。在本文結尾可使用這些程式碼範例的連結。
您在 Web 服務專案中包含 NuGet 封裝之後,您可以使用 VerifyBearerToken 方法,如中所示圖 9,驗證要求的持有人權杖。
圖 9 VerifyBearerToken 方法
private async Task<HttpStatusCode> VerifyBearerToken(
HttpRequestMessage request, string serviceBaseUrl, string expectedSender)
{
if (request.Headers.Authorization == null ||
!string.Equals(request.Headers.Authorization.Scheme, "bearer",
StringComparison.OrdinalIgnoreCase) ||
string.IsNullOrEmpty(request.Headers.Authorization.Parameter))
{
return HttpStatusCode.Unauthorized ;
}
string bearerToken = request.Headers.Authorization.Parameter;
ActionableMessageTokenValidator validator =
new ActionableMessageTokenValidator();
ActionableMessageTokenValidationResult result =
await validator.ValidateTokenAsync(bearerToken, serviceBaseUrl);
if (!result.ValidationSucceeded)
{
return HttpStatusCode.Unauthorized;
}
if (!string.Equals(result.Sender, expectedSender,
StringComparison.OrdinalIgnoreCase) ||
!result.ActionPerformer.EndsWith("@contoso.com",
StringComparison.OrdinalIgnoreCase))
{
return HttpStatusCode.Forbidden;
}
return HttpStatusCode.OK;
}
[HttpPost]
[Route("approve")]
public async Task<HttpResponseMessage> Approve([FromBody]JObject jBody)
{
HttpRequestMessage request = this.ActionContext.Request;
HttpStatusCode result = await VerifyBearerToken(
request, "https://api.contoso.com",
"expenseapproval@contoso.com");
switch (result)
{
case HttpStatusCode.Unauthorized:
return request.CreateErrorResponse(
HttpStatusCode.Unauthorized, new HttpError());
case HttpStatusCode.Forbidden:
HttpResponseMessage errorResponse =
this.Request.CreateErrorResponse(HttpStatusCode.Forbidden, new HttpError());
errorResponse.Headers.Add("CARD-ACTION-STATUS",
"Invalid sender or the action performer is not allowed.");
return errorResponse;
default:
break;
}
string expenseId = jBody["id"].ToString();
// Process and approve the expense report.
HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("CARD-ACTION-STATUS", "The expense was approved.");
return response;
}
首先,方法會驗證授權標頭中沒有的承載權杖。然後,它會初始化動作 ableMessageTokenValidator 的新執行個體,並呼叫 ValidateTokenAsync 方法。此方法會採用兩個參數。第一個是本身的持有人權杖。第二個是 Web 服務的基底 URL。如果您查看已解碼的 JWT 時,這是則 (對象) 宣告的值。基本上會在核發權杖的適用對象,也就是您的 Web 服務,但不是含任何其他 Web 服務。在此情況下,API 呼叫是http://api.contoso.com/expense/approve。宣告中的值會是基底 URL,即https://api.contoso.com。
方法會傳回 ActionableMessageTokenValidationResult 的執行個體。首先,您會檢查 ValidationSucceeded 的屬性。如果驗證成功,值會是 true。否則,會為 false。
結果也會包含兩個其他屬性,將會用於第三方。第一個是寄件者。這是寄件者宣告權杖中的值。這是可採取動作的訊息傳送之帳戶的電子郵件地址。第二個是 ActionPerformer sub 宣告的值。這是執行此動作之人員的電子郵件地址。在此範例中,只限於<@contoso.com>電子郵件地址可以核准或拒絕經費支出報表。您可以將程式碼取代您自己的更複雜的驗證。
重新整理卡
到目前為止,要提供意見反應給使用者的唯一方式是透過卡動作狀態標頭。標頭的值將顯示給使用者的智慧卡的保留區。另一個選項是重新整理卡傳回給使用者。這個概念是將目前的動作卡取代使用不同的卡片。有幾個原因,您想要這麼做的原因。比方說,核准經費支出報表後,您不想使用者可以核准或拒絕的經費支出報表一次。相反地,您想要告訴使用者已核准的經費支出報表。 圖 10顯示標記,您將會傳回。
圖 10 標記回到經費支出報表重新整理卡
{
"@context": "http://schema.org/extensions",
"@type": "MessageCard",
"hideOriginalBody": "true",
"title": "Expense report #98432019 was approved",
"sections": [{
"facts": [{
"name": "ID",
"value": "98432019"
}, {
"name": "Amount",
"value": "83.27 USD"
}, {
"name": "Submitter",
"value": "Kathrine Joseph"
}, {
"name": "Description",
"value": "Dinner with client"
}]
}]
}
您必須設定為 true,因此 Microsoft 伺服器會知道回應具有重新整理卡卡更新-在主體標頭的值。 圖 11顯示核准方法會傳回重新整理卡。
圖 11 核准方法會傳回重新整理卡
private HttpResponseMessage CreateRefreshCard(
HttpRequestMessage request, string actionStatus,
string expenseID, string amount, string submitter, string description)
{
string refreshCardFormatString = "{\"@context\": \"http://schema.org/extensions\",\"@type\": \"MessageCard\",\"hideOriginalBody\": \"true\",\"title\": \"Expense report #{0} was approved\",\"sections\": [{\"facts\": [{\"name\": \"ID\",\"value\": \"{0}\"},{\"name\": \"Amount\",\"value\": \"{1}\"},{\"name\": \"Submitter\",\"value\": \"{2}\"},{\"name\": \"Description\",\"value\": \"{3}\"}]}]}";
string refreshCardMarkup = string.Format(
refreshCardFormatString,
expenseID,
amount,
submitter,
description);
HttpResponseMessage response = request.CreateResponse(HttpStatusCode.OK);
Response.Headers.Add("CARD-ACTION-STATUS", actionStatus);
response.Headers.Add("CARD-UPDATE-IN-BODY", "true");
response.Content = new StringContent(refreshCardMarkup);
return response;
}
[HttpPost]
[Route("approve")]
public async Task<HttpResponseMessage> Approve([FromBody]JObject jBody)
{
HttpRequestMessage request = this.ActionContext.Request;
HttpStatusCode result = await VerifyBearerToken(
request, "https://api.contoso.com",
"expenseapproval@contoso.com");
switch (result)
{
case HttpStatusCode.Unauthorized:
return request.CreateErrorResponse(
HttpStatusCode.Unauthorized, new HttpError());
case HttpStatusCode.Forbidden:
HttpResponseMessage errorResponse =
this.Request.CreateErrorResponse(
HttpStatusCode.Forbidden, new HttpError());
errorResponse.Headers.Add("CARD-ACTION-STATUS",
"Invalid sender or the action performer is not allowed.");
return errorResponse;
default:
break;
}
string expenseId = jBody["id"].ToString();
// Process and approve the expense report.
return CreateRefreshCard(
request,
"The expense was approved.",
"98432019",
"83.27 USD",
"Jonathan Kiev",
"Dinner with client");
}
總結
可採取動作的訊息,讓使用者安全的方式完成在 Outlook 中的工作。它使用中桌面的 Outlook 和 Outlook Web Access 今天,此功能即將推出將適用於 Mac 的 Outlook 和 Outlook Mobile。它會直接實作可採取動作的訊息。首先,您必須將必要的標記加入至您要傳送出的電子郵件。其次,您必須確認您的 Web 服務以傳送 microsoft 的持有人權杖。可採取動作的訊息會讓您的使用者,幸福且更具生產力。沒有這麼多超過有關可採取動作的訊息本文可以列舉。請瀏覽bit.ly/2rAD6AZ完整的參考和程式碼範例的連結。
我想要了解 Sohail Zafar,Edaena Salinas Jasso,Vasant Kumar Tiwari、 標記 Encarnacion 和 Miaosen Wang 協助人員會檢閱本文的文法、 拼字和流程。
Woon Kiat 黃是軟體工程師,從在 Microsoft Research 知識技術群組。他密切與 Outlook 小組可採取動作的訊息傳遞。請連絡他在wowong@microsoft.com。
非常感謝下列 Microsoft 技術專家檢閱這篇文章:Pretish 林肯、 David Claux、 標記 Encarnacion 和 Patrick Pantel