1.始めに
-
System.Web.Mail名前空間(SmtpClient)はMS社は使用禁止に近い非推奨にしています。代わりにMailKit等を使うよう通知されています。
-
メールの送信はSMTPサーバに一方的に送り付けるだけで送信され、後は送信済みトレイに自動的に格納されるので簡単です。
-
メール操作には受信,フォルダ,フラグ等の処理が必要になるが資料はこのQiitaにも皆無なので簡単にまとめてみました。
-
詳しくは開発元のhttps://mimekit.net/docs/html/Introduction.htm を参照。
MailKit、面倒なソケットをラッピングして数行でIMAP4が使える凄さ。
2.メールサーバが扱えると下記の様な事が出来る
-
送受信部を専用APP化すればローカルLANから離れた場所でも双方向通信が張れる。 OSI参照モデルでいう所の(UDP)トランスポート層辺り。
-
Eメールという極当たり前に使用できる環境があればVPN、WEBサーバ等を用意しなくても良い。
-
例えばノートPCにはGPSが内蔵されているものがあり、経緯度を取得してそれを独自に取り決めたフォーマットに整形して適当な間隔で送る。
-
管理側では受信専用のAPPを作り受信したメール内容から経緯度を取得し、地図上にプロットすればリアルタイムで各端末の位置がわかる。
-
バスの位置情報を利用者サービスとして開示している運航会社があるがこの仕組みを使えば格安・簡単に構築できる。
(GPSが内蔵されてなければ https://qiita.com/inf102/items/ad37aafdd52f95c3bda6 こんな感じで簡単に実装できる。 -
メールを頻繁に使う業務の場合、汎用メーラを使うよりその業務・作業に特化した専用APPを用意すると業務効率が向上する。それを実現するには操作法を理解する必要が有る。この記事はそれらを意識して記述したものである。
以前、倉庫の在庫状況を報告する仕組みを構築する話があってWebサーバもVPNもない環境だったのでこの仕組みで作る予定だったがPrj自体他に移管。
1.INBOX 件名等表示、CSV化
// INBOX 件名等表示 CSV化 L()はリストボックスに追記する関数
private async void Button_Click_5(object sender, RoutedEventArgse) {
StreamWriter sw = new StreamWriter(@"C:\IMAP4.csv", false,System.Text.Encoding.GetEncoding("shift_jis"));
using (var imap4 = new MailKit.Net.Imap.ImapClient()){
try{
await imap4.ConnectAsync("xxxxxxxxx",993,SecureSocketOptions.SslOnConnect);
}catch{
L ("Button_Click_5 exception");
return;
}
await imap4.AuthenticateAsync (USR, PW);
await imap4.Inbox.OpenAsync (FolderAccess.ReadOnly);
var inbox = await imap4.GetFolderAsync ("INBOX");
//////////////////////////////////////////////////////////
// 全指定の場合
var uids = await imap4.Inbox.SearchAsync(SearchQuery.All);
L("\r\n----INBOX内件名-----");
foreach(var uid in uids){
var message = await imap4.Inbox.GetMessageAsync(uid);
if (message.Subject.ToString()=="") message.Subject="(件名なし)";
L (uid.ToString()+" "+ message.From.ToString()+ message.Subject.ToString());
L (message.TextBody.ToString()); // 本文
sw.Write(message.Subject+","+message.TextBody.ToString());
sw.WriteLine();
}
await imap4.DisconnectAsync(true);
sw.Close();
}
}
2.フォルダ作成
- プログラムでIMAP4フォルダを作るケースはあまりないだろうがこんな感じ
- 作成しても未購読のままでメーラで非表示になってたり、試行錯誤でパラメータ変えて表示できたと思ったら未購読なのに表示されてたり。良くわからん。メーラで購読フォルダに設定する必要が有る。
- INBOXの場合はimap4.GetFolderAsync ("INBOX");
// フォルダ作成
// 参考URL https://github.com/jstedfast/MailKit/blob/master/MailKit/Net/Imap/ImapUtils.cs
// MailKit/MailKit/Net/Imap/ImapUtils.cs
public async void MakeFolder() {
using (var imap4 = new MailKit.Net.Imap.ImapClient()){
try{
await imap4.ConnectAsync(ADDR,993,SecureSocketOptions.SslOnConnect);
}catch(Exception e){
L ("\r\nMakeFolder ConnectAsync exception");
L (e.Message.ToString()+"\r\n");
return;
}
await imap4.AuthenticateAsync (USR, PW);
await imap4.Inbox.OpenAsync (FolderAccess.ReadWrite);
// INBOX外の直下にフォルダ作成 何故か未購読フォルダのまま
var toplevel = imap4.GetFolder(imap4.PersonalNamespaces[0]);
var ma = await toplevel.CreateAsync("FOLDER1",(FolderAttributes.Subscribed) == 0);
// INBOXに作成する場合は確かこんな感じ これも未購読フォルダのまま
// var inbox_top = await imap4.GetFolderAsync ("INBOX");
// var inbox2=inbox_top.Create("B1", (toplevel.Attributes & FolderAttributes.NoSelect)) == 0);
}
}
3.フォルダ間移動
- INBOX内のフォルダとINBOX外のフォルダで指定の仕方が少し違う
- ここで示している特定メールの操作方法は件名表示などに利用可
// INBOXからINBOX内トップフォルダに移動
private async void Button_Click_11(object sender, RoutedEventArgs e) {
using (var imap4= new MailKit.Net.Imap.ImapClient()){
imap4.Connect(ADDR,993,SecureSocketOptions.SslOnConnect);
imap4.Authenticate(USR, PW);
imap4.Inbox.Open(MailKit.FolderAccess.ReadWrite);
if (!imap4.Inbox.IsOpen == true) throw new Exception("MoveEmail Inbox is not open.");
IMailFolder FolderName;
// INBOX内のTOP_FOLDER
FolderName= await imap4.Inbox.GetSubfolderAsync ("AAA");
// var FolderName2=await
FolderName.GetSubfolderAsync("DDD"); // AAAの配下にあるDDDに移動する場合
// var range = new UniqueIdRange (new UniqueId ((uint)123457), UniqueId.MaxValue); // sample
// MAILUniqueId == 878番を指定する場合
// 一件移動用の関数があるかも知れない わからないので同番号を指定している
var range = new UniqueIdRange (new UniqueId ((uint) 878),new UniqueId ((uint) 878)); // MAIL_ID
var query = SearchQuery.Uids(range);
var uids = await imap4.Inbox.SearchAsync(query);
try {
await imap4.Inbox.MoveToAsync (uids, FolderName);
// await imap4.Inbox.MoveToAsync (uids, FolderName2);
// AAAの配下にあるDDDに移動する場合
}
catch (Exception ex){
await imap4.DisconnectAsync(true);
L(ex.Message.ToString());
return;
}
imap4.Disconnect(true);
}
}
4.メール送信
- 送信は簡単でSMTPサーバに送り付けるだけ
- 添付する場合は https://qiita.com/inf102/items/c47506fcdf8ec8cac9fd 等を参照
// 送信 漢字コードはiso-2022-jp指定
private async void Button_Click_1(object sender, RoutedEventArgse) {
var enc = System.Text.Encoding.GetEncoding("iso-2022-jp");
using (var smtp = new MailKit.Net.Smtp.SmtpClient()) {
await smtp.ConnectAsync(ADDR, 465,MailKit.Security.SecureSocketOptions.SslOnConnect);
await smtp.AuthenticateAsync (USR, PW);
var mail = new MimeKit.MimeMessage();
mail.From.Add (new MimeKit.MailboxAddress("xxxxxc.jp", "xxxxxac.jp"));
mail.To.Add (new MimeKit.MailboxAddress("xxxxxx.jp", "xxxxac.jp")); // 宛先表記名 . 送信先Addr
mail.Headers.Replace (MimeKit.HeaderId.Subject, enc, "メールの件名");
MimeKit.TextPart textPart = new MimeKit.TextPart("plain");
textPart.SetText(enc, "メールの本文\n\n以上");
textPart.ContentTransferEncoding =MimeKit.ContentEncoding.SevenBit;
mail.Body = textPart;
await smtp.SendAsync(mail);
await smtp.DisconnectAsync(true);
}
L("送信しました");
}
5.INBOX件名等ソートして表示
- IMAP4はメール一件に二つのIDを持ってます。メッセージ番号とユニークIDです
- メッセージ番号はメールが削除されると新に全て振り直しになります
0,1,2,3 で2削除後は0,1,2 となり3が2になります。0から最後まで連続になる - ユニークID、Unique Identifier (UID)は削除した場合、その番号は欠番になります。削除してもID同一が保証されます
private async void Button_Click_51(object sender, RoutedEventArgse) {
using (var imap4 = new MailKit.Net.Imap.ImapClient()){
try{
await imap4.ConnectAsync(ADDR,993,SecureSocketOptions.SslOnConnect);
}catch{
L ("Button_Click_5 exception");
return;
}
await imap4.AuthenticateAsync (USR, PW);
await imap4.Inbox.OpenAsync (FolderAccess.ReadOnly);
var inbox = await imap4.GetFolderAsync ("INBOX");
//////////////////////////////////////////////////////////
// 全指定の場合
var uids = await imap4.Inbox.SearchAsync(SearchQuery.All);
var messages = await inbox.FetchAsync(0, -1,MessageSummaryItems.UniqueId | MessageSummaryItems.Full | MessageSummaryItems.All );
var sortedMessages = messages.OrderByDescending (msg => msg.Date).ToList();
foreach (var message in sortedMessages){
L ( message.UniqueId.ToString()+" "+message.NormalizedSubject.ToString()+" "+message.Date.ToString());
}
await imap4.DisconnectAsync(true);
}
}
}
6.フォルダ名、ディスク使用可能量(DISKQUOTA)表示等
public async void Rcv() {
using (var imap4 = new MailKit.Net.Imap.ImapClient()){
try{
await imap4.ConnectAsync(ADDR,993,SecureSocketOptions.SslOnConnect);
}catch{
L ("ConnectAsync exception Rcv()");
return;
}
await imap4.AuthenticateAsync(USR, PW);
await imap4.Inbox.OpenAsync (FolderAccess.ReadWrite);
var inbox = await imap4.GetFolderAsync ("INBOX");
// DISK_SIZE
L("---DISKQUOTA_SIZE---\r\n"+(inbox.GetQuota().StorageLimit.ToString()+"bytes\r\n")) ;
L("---USED_SIZE---\r\n"+(inbox.GetQuota().CurrentStorageSize.ToString()+"bytes\r\n"));
// ALL
var uids = await imap4.Inbox.SearchAsync(SearchQuery.All);
L ("---INBOX メール件数---\r\n "+ (uids.Count).ToString()+"件\r\n");
L ("---INBOX トップフォルダ一覧---");
foreach (var folder in await inbox.GetSubfoldersAsync(false)){
L(folder.Name);
}
L("\r\n---INBOX外.トップフォルダ一覧---");
var personal = imap4.GetFolder(imap4.PersonalNamespaces[0]);
foreach (var folder in personal.GetSubfolders(false)){
if (folder.Name=="INBOX") continue;
LIST1.Items.Add(folder.Name);
}
var vv=personal.GetSubfolder("バックアップ");
foreach (var folder in vv.GetSubfolders(false)){
if (folder.Name=="INBOX") continue;
LIST1.Items.Add(folder.Name);
}
}
}
7.補足
-
INBOX外フォルダ名の表示について、
var personal = imap4.GetFolder(imap4.PersonalNamespaces[0]);
これでINBOX外トップフォルダにアクセスする。
personal.GetSubfolders()で配下のリストアップが可能。
更にその配下にある「バックアップ」フォルダの中身をリストアップするには、
personal.GetSubfolder("バックアップ"); で取得して、
foreach (var folder in vv.GetSubfolders で回す。 -
これらを使用すれば OUTLOOK等のメーラのツリー構造が表示できる。
(かなり面倒なコードにはなる) -
INBOXとは新規受信用の場所でIMAP4サーバが自動的に着信メールを格納する所。
IMAP4-RFCでは仕様上INBOXのリネームは許可されているが普通は許可しない。 -
メッセージフラグ
各メールには現在6個のフラグがある。
imap4.Inbox.SetFlagsAsync(uids,MessageFlags.Recent ,true); // こんな感じで指定
フラグ名 | 説明 | ||
---|---|---|---|
¥recent | [新着] 一般的にメーラでは件名は太字で表示させている | ||
¥seen | [既読] 太字から通常の太さに戻して表示が多い | ||
¥deleted | [削除予定] プログラム側で使うものでユーザが削除を希望した時プログラムの方で削除フラグを付ける時のもの | ||
¥draft | [下書き] | ||
¥flaged | [旗付け] 意味はAPP、ユーザが決める。重要メールとか後で読むため等 | ||
¥mdnsent | [開封要求通知] 開封通知メールに対して処理を行ったもの |