C# で SMTP サーバーに接続したい(入門編)!!!

投稿者: | 2018年4月22日

C# を使用して SMTP サーバーに接続する方法をまとめます。

目次

やりたいこと

SMTP サーバーに接続する C# コードを作成し、メールの送信を目指します。
今回は勉強のために、SMTP コマンドを使用してサーバーと送受信を行うところから作成します。複数メールの送信、複数宛への送信、CC、BCC、SMTP 認証、SSL 接続については実装せずに最低限の単一メールの送信のみを目指します。

事前準備

1.SMTP サーバーの接続情報を用意する
接続したいSMTP サーバーの下記情報を用意してください。
・SMTP サーバー名(ドメイン名)

2.メールの送信先情報を用意する
メールを送信したい 送信先情報を用意してください。
・送信先メールアドレス
・送信先ユーザー名

手順概要

手順1.SMTP サーバーに接続する関数を作成する
手順2.SMTP サーバーにメールを送信する

手順1.SMTP サーバーに接続する関数を作成する

下回り関数 を実装する

送信メッセージには Base64 への変換が欠かせないため、専用の変換関数を作成します。今回は Common クラスという汎用クラスを作ってそこに入れることにします。

class Common
{
    // 指定された文字コードでエンコードし、その後 Base64 に変換
    public static string GetBase64(string str, string charSetName = "utf-8")
    {
        var enc = Encoding.GetEncoding(charSetName);
        return Convert.ToBase64String(enc.GetBytes(str));
    }
}

SMTP サーバーへ送信するメールクラスを作成します。

/// <summary>
/// 送信用のメールクラス
/// </summary>
public class Mail
{
    /// <summary>
    /// 送信者のメールアドレス
    /// </summary>
    public MailAddress From { get; set; }
    public MailAddress FromEncode( string encode ) { return EncodeEmail(From, encode); }

    /// <summary>
    /// 送信先のメールアドレス
    /// </summary>
    public MailAddress To { get; set; }
    public MailAddress ToEncode(string encode) { return EncodeEmail(To, encode); }

    /// <summary>
    /// メールの件名
    /// </summary>
    public string Subject { get; set; }

    /// <summary>
    /// メールの本文
    /// </summary>
    public string Body { get; set; }

    // メールアドレスの表記名部分が非 ASCII のときにはエンコード
    private MailAddress EncodeEmail(MailAddress email, string encode)
    {
        if (email.DisplayName != "" && !Regex.IsMatch(email.DisplayName, @"^\p{IsBasicLatin}+$"))
        {
            var enc = Encoding.GetEncoding(encode);
            var dispName = string.Format("=?{0}?B?{1}?=", encode,
                Common.GetBase64(email.DisplayName, encode));
            var mail = new MailAddress(email.Address, dispName);
            return mail;
        }
        return email;
    }
}

SMTP サーバーとメッセージを送受信するためのメッセージクラスを作成します。サーバー間とやり取りするメッセージの作成、送信、受信、送受信をする関数を実装します。

public class Message
{
    // バッファ長
    private const int _bufferLen = 1024;

    private System.IO.Stream stream;
    private string encode;

    public Message(System.IO.Stream stream, string encode)
    {
        this.stream = stream;
        this.encode = encode;
    }

    // メッセージを送受信
    public void SendReceive(string sendStr, List trueList, bool repeat_until_no_resp, out string receiveStr, string logPrefix)
    {
        Send(sendStr, logPrefix);
        if (!Receive(trueList, repeat_until_no_resp, out receiveStr))
            throw new ApplicationException(receiveStr);
    }

    // メッセージを送信
    private void Send(string sendStr, string logPrefix)
    {
        var enc = Encoding.GetEncoding(encode);

        // byte 型配列に変換
        var data = enc.GetBytes(sendStr + "\r\n");
        stream.Write(data, 0, data.Length);
        Debug.WriteLine(logPrefix + sendStr);
    }

    // メッセージを受信
    public bool Receive(List trueList, bool repeat_until_no_resp, out string receiveStr)
    {
        var enc = Encoding.GetEncoding(encode);
        var data = new byte[_bufferLen];
        int len = 0;
        bool ret = false;
        receiveStr = "";

        using (var memStream = new System.IO.MemoryStream())
        {
            // 受信
            while (true)
            {
                try
                {
                    len = stream.Read(data, 0, data.Length);

                    memStream.Write(data, 0, len);
                    // デコード
                    receiveStr = enc.GetString(memStream.ToArray());

                    if (!new Regex("^\\d{3}").IsMatch(receiveStr))
                    {
                        // 先頭3文字が数字ではない場合、破棄
                        memStream.Seek(0, System.IO.SeekOrigin.Begin);
                        receiveStr = "";
                    }
                }
                catch (System.IO.IOException ex)
                {
                    len = 0;
                    if ((ex.InnerException as System.Net.Sockets.SocketException).ErrorCode != 10060)
                    {
                        // 無応答以外の例外発生
                        receiveStr = "";
                        break;
                    }
                }

                ushort recNo = 0;
                if (receiveStr != "") UInt16.TryParse(receiveStr.Substring(0, 3), out recNo);

                if (len == 0)
                {
                    if (trueList.Contains(recNo) && receiveStr.EndsWith("\r\n"))
                    {
                        ret = true;
                    }
                    break;
                }

                if (!repeat_until_no_resp)
                {
                    if (trueList.Contains(recNo) && receiveStr.EndsWith("\r\n"))
                    {
                        ret = true;
                        break;
                    }
                }
            }

            memStream.Close();
        }

        Debug.WriteLine(receiveStr);

        return ret;
    }

    // メッセージを作成
    public StringBuilder Create(Mail mail)
    {
        var data = new StringBuilder(_bufferLen);

        // メールヘッダ
        data.Append("From: " + mail.FromEncode(encode) + "\r\n");
        data.Append("To: " + mail.ToEncode(encode) + "\r\n");

        if (mail.Subject != "" && !Regex.IsMatch(mail.Subject, @"^\p{IsBasicLatin}+$"))
        {
            // Subject に非 ASCII 文字が含まれている場合、Base64 でエンコード
            data.Append("Subject: " + string.Format("=?{0}?B?{1}?=\r\n",
                encode, Common.GetBase64(mail.Subject, encode)));
        }
        else
        {
            data.Append("Subject: " + mail.Subject + "\r\n");
        }
        
        var date = DateTime.Now.ToString(@"ddd, dd MMM yyyy HH\:mm\:ss",
            System.Globalization.CultureInfo.CreateSpecificCulture("en-US"));
        var timeZone = DateTimeOffset.Now.ToString("%K").Replace(":", "");
        data.Append(string.Format("Date: {0} {1}\r\n", date, timeZone));
        data.Append(string.Format("Message-ID: <{0}@{1}>\r\n", Guid.NewGuid(), mail.From.Host));
        data.Append("MIME-Version: 1.0\r\n");
        data.Append("Content-Type: text/plain; charset=" + encode + "\r\n");
        data.Append("Content-Transfer-Encoding: 7bit\r\n");

        // 空行
        data.Append("\r\n");

        // メールボディ
        data.Append(Regex.Replace(mail.Body, @"^\.", "..", RegexOptions.Multiline) + "\r\n"); // see RFC 5321 4.5.2 Transparency

        // メール終端
        data.Append(".");

        return data;
    }
}

SMTP サーバーへメールを送信する関数 を実装する

SMTP サーバーと接続し、メールの送信を行う Smtp クラスを作成します。サーバー間とやり取りするメッセージの作成、送信、受信、送受信をする関数を実装します。

/// <summary>
/// SMTP サーバーとの交信を行う
/// </summary>
public class Smtp
{
    /// <summary>
    /// SMTP サーバーの名前
    /// </summary>
    public string serverName { get; set; }

    /// <summary>
    /// SMTP サーバーの受付ポート番号
    /// </summary>
    public int serverPort { get; set; }

    /// <summary>
    /// 本文のエンコーディングに使用する IANA に登録されている名前
    /// </summary>
    public string encode { get; set; }

    /// <summary>
    /// 受信タイムアウト
    /// </summary>
    public int readTimeout { get; set; }

    /// <summary>
    /// メールを送信する
    /// </summary>
    /// <exception cref="InvalidOperationException">
    /// Thrown when... プロパティ不足
    /// </exception>
    /// <exception cref="SocketException">
    /// Thrown when... SMTPサーバー接続失敗
    /// </exception>
    /// <exception cref="System.Security.Authentication.AuthenticationException">
    /// Thrown when... SSL 接続失敗
    /// </exception>
    /// <exception cref="ApplicationException">
    /// Thrown when... SMTPサーバー通信エラー
    /// </exception>
    /// <param name="mail">送信するメール</param>
    /// <returns></returns>
    public void Send(Mail mail)
    {
        if (serverName == "")
            throw new InvalidOperationException("ServerName is empty.");
        if (Encoding.GetEncodings().Where(c => c.Name.ToUpper() == encode.ToUpper()).Count() == 0)
            throw new InvalidOperationException("Encoding name not found(encoding: " + encode + ")");

        // サーバー接続
        Debug.WriteLine("Connecting...");
        using (var sock = new TcpClient())
        {
            try
            {
                sock.Connect(serverName, serverPort);
                Debug.WriteLine("Connected!");
                Debug.WriteLine("Socket open!");
                using (var stream = sock.GetStream())
                {
                    stream.ReadTimeout = readTimeout;

                    try
                    {
                        string rstr = "";
                        string sstr = "";

                        Message message = new Message(stream, encode);

                        if (!message.Receive(new List<UInt16> { 220 }, false, out rstr))
                            throw new ApplicationException("Server Not Ready");

                        sstr = "EHLO " + System.Net.Dns.GetHostName();
                        message.SendReceive(sstr, new List<UInt16> { 250 }, true, out rstr, "[S]"); //無応答となるまで受信し続ける

                        // SMTP
                        SMTPConnect(stream, mail);
                    }
                    finally
                    {
                        Debug.WriteLine("Socket close!");
                        stream.Close();
                    }
                }
            }
            catch (System.Net.Sockets.SocketException e)
            {
                Debug.WriteLine("Socket error! code: " + e.ErrorCode.ToString() + "; " + e.Message);
                throw;
            }
            finally
            {
                // サーバー接続のクローズ
                Debug.WriteLine("Connection close!");
                sock.Close();
            }
        }
    }

    // SMTP 接続
    private void SMTPConnect(System.IO.Stream stream, Mail mail)
    {
        string rstr = "";
        string sstr = "";

        Message message = new Message(stream, encode);

        try
        {
            sstr = "MAIL FROM:<" + mail.From.Address + ">";
            message.SendReceive(sstr, new List<UInt16> { 250 }, false, out rstr, "[C]");
        }
        catch (ApplicationException e)
        {
            sstr = "MAIL FROM:" + mail.From.Address;
            message.SendReceive(sstr, new List<UInt16> { 250 }, false, out rstr, "[C]");
        }

        try
        {
            sstr = "RCPT TO:<" + mail.To.Address + ">";
            message.SendReceive(sstr, new List<UInt16> { 250 }, false, out rstr, "[C]");
        }
        catch (ApplicationException e)
        {
            sstr = "RCPT TO:" + mail.To.Address;
            message.SendReceive(sstr, new List<UInt16> { 250 }, false, out rstr, "[C]");
        }

        sstr = "DATA";
        message.SendReceive(sstr, new List { 354 }, false, out rstr, "[C]");

        // メールデータ作成
        sstr = message.Create(mail).ToString();
        message.SendReceive(sstr, new List { 250 }, false, out rstr, "[C]mail DATA:\r\n");

        sstr = "QUIT";
        message.SendReceive(sstr, new List { 221 }, false, out rstr, "[C]");
    }
}

手順2.SMTP サーバーにメールを送信する

手順1で作成したクラスを使って、SMTP サーバーへメールを送信してみましょう

下記コードを実行関数の中に追加しましょう。事前準備で用意した、SMTP サーバー名、送信先メールアドレス、送信先ユーザー名を使用します。

var mail = new Mail {
    From = new System.Net.Mail.MailAddress("test@test.com", "test"),
    To = new System.Net.Mail.MailAddress("送信先アドレス", "送信先ユーザー名"),
    Subject = "test",
    Body = "こんにちは\r\n\r\n今日はいい天気ですね。"
};

var smtp = new Smtp
{
    serverName = "SMTP サーバー名",
    serverPort = 25,
    encode = "ISO-2022-JP",
    readTimeout = 500
};

try
{
    smtp.Send(mail);
    MessageBox.Show("メールを送信しました。", "メール送信", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (ApplicationException ex)
{
    MessageBox.Show("メールの送信に失敗しました。エラー内容: " + ex.Message,
        "メール送信エラー", MessageBoxButton.OK, MessageBoxImage.Warning);
}
catch (InvalidOperationException ex)
{
    MessageBox.Show("メールの送信に必要な設定値が不足しています。エラー内容: " + ex.Message,
        "メール送信エラー", MessageBoxButton.OK, MessageBoxImage.Warning);
}
catch (System.Net.Sockets.SocketException ex)
{
    MessageBox.Show("サーバーと接続ができませんでした。エラー内容: " + ex.Message,
        "メール送信エラー", MessageBoxButton.OK, MessageBoxImage.Warning);
}
catch (System.Security.Authentication.AuthenticationException ex)
{
    MessageBox.Show("サーバーと SSL 接続ができませんでした。エラー内容: " + ex.Message,
        "メール送信エラー", MessageBoxButton.OK, MessageBoxImage.Warning);
}

まとめ

C#で SMTP サーバーに対して、実際にメールを送信する関数とクラスを作成してみました。今回お見せした Smtp クラスは、SMTP 認証や SSL 接続なども追加で実装が可能です。今後の記事で書いていきたいと思いますので、その時は是非お試しください。

関連するページ

その他の備忘録も以下のページでまとめていますので、よろしければアクセスしてください。

以上!!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です