C#邮件发送问题(一)

邮件发送需考虑很多因素,包括发送邮件客户端(一般编码实现),发送和接收邮件服务器设置等。如果使用第三方邮件服务器作为发送服务器,就需要考虑该服务器的发送限制,(如发送邮件时间间隔,单位时间内发送邮件数量,是否使用安全连接SSL),同时无论使用第三方还是自己的邮件服务器都还需要考虑接收邮件服务器的限制。为理清思路,下面我们简单回顾电子邮件系统的基本网络结构和邮件发送接收流程。

一、电子邮件系统的基本网络结构

如下图:

邮件发送接收一般经过以下几个节点:

  • 发送邮件客户端(Mail User Agent, MUA) : Formail, Outlook, Webmail, C# Code, Java
    Code, etc.

  • 发送邮件服务器(Mail Transfer Agent, MTA) : hMailServer, Exchange, TurboMail, etc.

  • 接收邮件服务器(Mail Transfer Agent, MTA)

  • 接收邮件客户端(Mail User Agent, MUA)

发送过程中客户端与服务器及服务器之间使用SMTP协议,在接收过程中客户端与服务端之间使用POP3或IMAP(POP3的替代协议,支持邮件摘要显示和脱机操作)。邮件发送可简单认为是一种文件传输,但与FTP实时文件传输不同,各邮件服务器会保存邮件文件本身,直至被下一个邮件服务器或客户端接收,类似异步与同步的差别。

由上可知,为顺利发送和接受邮件,客户端设置或编码需要严格适应邮件服务器的要求。对于发送邮件需明确:SMTP服务器地址和端口(默认端口25),是否使用安全连接(SSL),验证凭据(用户和密码),及更加细节的邮件格式,邮件编码方式等;对于接收邮件需明确:POP3或IMAP服务器地址和端口(POP3默认端口110,IMAP默认端口143),是否使用安全连接(SSL),验证凭据(用户和密码)

二、C#下发送邮件组件及测试

C#下发送邮件的组件使用较为普遍的有以下三个:System.Net.Mail, OpenSmtp,
LumiSoft.Net。下面我们就分别对他们进行测试。

发送邮件至少需要发送邮件服务器信息和邮件信息,因此我们建立Host和Mail两个配置类。


    public class ConfigHost
{
public string Server { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool EnableSsl { get; set; }
}

public class ConfigMail
{
public string From { get; set; }
public string[] To { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public string[] Attachments { get; set; }
public string[] Resources { get; set; }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

同时定义一个统一的接口ISendMail,以方便测试和比较。


    public interface ISendMail
{
void CreateHost(ConfigHost host);
void CreateMail(ConfigMail mail);
void CreateMultiMail(ConfigMail mail);
void SendMail();
}

1、使用System.Net.Mail

System.Net.Mail属于.Net Framework 的一部分,.Net2.0以后可以使用这个组件。


    using System.Net.Mail;
public class UseNetMail : ISendMail
{
private MailMessage Mail { get; set; }
private SmtpClient Host { get; set; }

public void CreateHost(ConfigHost host)
{
Host = new SmtpClient(host.Server, host.Port);
Host.Credentials = new System.Net.NetworkCredential(host.Username, host.Password);
Host.EnableSsl = host.EnableSsl;
}

public void CreateMail(ConfigMail mail)
{
Mail = new MailMessage();
Mail.From = new MailAddress(mail.From);

foreach (var t in mail.To)
Mail.To.Add(t);

Mail.Subject = mail.Subject;
Mail.Body = mail.Body;
Mail.IsBodyHtml = true;
Mail.BodyEncoding = System.Text.Encoding.UTF8;
}

public void CreateMultiMail(ConfigMail mail)
{
CreateMail(mail);

Mail.AlternateViews.Add(AlternateView.CreateAlternateViewFromString("If you see this message, it means that your mail client does not support html.", Encoding.UTF8, "text/plain"));

var html = AlternateView.CreateAlternateViewFromString(mail.Body, Encoding.UTF8, "text/html");
foreach (string resource in mail.Resources)
{
var image = new LinkedResource(resource, "image/jpeg");
image.ContentId = Convert.ToBase64String(Encoding.Default.GetBytes(Path.GetFileName(resource)));
html.LinkedResources.Add(image);
}
Mail.AlternateViews.Add(html);

foreach (var attachment in mail.Attachments)
{
Mail.Attachments.Add(new Attachment(attachment));
}
}

public void SendMail()
{
if (Host != null && Mail != null)
Host.Send(Mail);
else
throw new Exception("These is not a host to send mail or there is not a mail need to be sent.");
}
}

2、使用OpenSmtp

开源的发送邮件组件,可以在这里获得源码。但是OpenSmtp目前不支持SSL。


    using OpenSmtp.Mail;
public class UseOpenSmtp : ISendMail
{
private MailMessage Mail { get; set; }
private Smtp Host { get; set; }

public void CreateHost(ConfigHost host)
{
Host = new Smtp(host.Server, host.Username, host.Password, host.Port);
}

public void CreateMail(ConfigMail mail)
{
Mail = new MailMessage();
Mail.From = new EmailAddress(mail.From);
foreach (var t in mail.To)
Mail.AddRecipient(t, AddressType.To);

Mail.HtmlBody = mail.Body;
Mail.Subject = mail.Subject;
Mail.Charset = "UTF-8";
}

public void CreateMultiMail(ConfigMail mail)
{
CreateMail(mail);
foreach (var attachment in mail.Attachments)
{
Mail.AddAttachment(attachment);
}
foreach (var resource in mail.Resources)
{
Mail.AddImage(resource, Convert.ToBase64String(Encoding.Default.GetBytes(Path.GetFileName(resource))));
}
}

public void SendMail()
{
if (Host != null && Mail != null)
Host.SendMail(Mail);
else
throw new Exception("These is not a host to send mail or there is not a mail need to be sent.");
}

3、使用LumiSoft.Net

LumiSoft.Net是非常强大的开源组件,不仅仅发送邮件,同样也可用于接收邮件,是个人认为最好的开源组件了。在这里可以详细了解LumiSoft.Net组件的命名空间,也可以在这里下载其源码和样例。


    using LumiSoft.Net.SMTP.Client;
using LumiSoft.Net.AUTH;
using LumiSoft.Net.Mail;
using LumiSoft.Net.MIME;
public class UseLumiSoft : ISendMail
{
private SMTP_Client Host { get; set; }
private Mail_Message Mail { get; set; }

public void CreateHost(ConfigHost host)
{
Host = new SMTP_Client();
Host.Connect(host.Server, host.Port, host.EnableSsl);
Host.EhloHelo(host.Server);
Host.Auth(Host.AuthGetStrongestMethod(host.Username, host.Password));
}

public void CreateMail(ConfigMail mail)
{
Mail = new Mail_Message();
Mail.Subject = mail.Subject;
Mail.From = new Mail_t_MailboxList();
Mail.From.Add(new Mail_t_Mailbox(mail.From, mail.From));
Mail.To = new Mail_t_AddressList();
foreach (var to in mail.To)
{
Mail.To.Add(new Mail_t_Mailbox(to, to));
}
var body = new MIME_b_Text(MIME_MediaTypes.Text.html);
Mail.Body = body; //Need to be assigned first or will throw "Body must be bounded to some entity first" exception.
body.SetText(MIME_TransferEncodings.Base64, Encoding.UTF8, mail.Body);
}

public void CreateMultiMail(ConfigMail mail)
{
CreateMail(mail);

var contentTypeMixed = new MIME_h_ContentType(MIME_MediaTypes.Multipart.mixed);
contentTypeMixed.Param_Boundary = Guid.NewGuid().ToString().Replace("-", "_");
var multipartMixed = new MIME_b_MultipartMixed(contentTypeMixed);
Mail.Body = multipartMixed;

//Create a entity to hold multipart/alternative body
var entityAlternative = new MIME_Entity();
var contentTypeAlternative = new MIME_h_ContentType(MIME_MediaTypes.Multipart.alternative);
contentTypeAlternative.Param_Boundary = Guid.NewGuid().ToString().Replace("-", "_");
var multipartAlternative = new MIME_b_MultipartAlternative(contentTypeAlternative);
entityAlternative.Body = multipartAlternative;
multipartMixed.BodyParts.Add(entityAlternative);

var entityTextPlain = new MIME_Entity();
var plain = new MIME_b_Text(MIME_MediaTypes.Text.plain);
entityTextPlain.Body = plain;
plain.SetText(MIME_TransferEncodings.Base64, Encoding.UTF8, "If you see this message, it means that your mail client does not support html.");
multipartAlternative.BodyParts.Add(entityTextPlain);

var entityTextHtml = new MIME_Entity();
var html = new MIME_b_Text(MIME_MediaTypes.Text.html);
entityTextHtml.Body = html;
html.SetText(MIME_TransferEncodings.Base64, Encoding.UTF8, mail.Body);
multipartAlternative.BodyParts.Add(entityTextHtml);

foreach (string attachment in mail.Attachments)
{
multipartMixed.BodyParts.Add(Mail_Message.CreateAttachment(attachment));
}

foreach (string resource in mail.Resources)
{
var entity = new MIME_Entity();
entity.ContentDisposition = new MIME_h_ContentDisposition(MIME_DispositionTypes.Inline);
entity.ContentID = Convert.ToBase64String(Encoding.Default.GetBytes(Path.GetFileName(resource))); //eg.<img src="cid:ContentID"/>
var image = new MIME_b_Image(MIME_MediaTypes.Image.jpeg);
entity.Body = image;
image.SetDataFromFile(resource, MIME_TransferEncodings.Base64);
multipartMixed.BodyParts.Add(entity);
}
}

public void SendMail()
{
if (Host != null && Mail != null)
{
foreach (Mail_t_Mailbox from in Mail.From.ToArray())
{
Host.MailFrom(from.Address, -1);
}
foreach (Mail_t_Mailbox to in Mail.To)
{
Host.RcptTo(to.Address);
}
using (var stream = new MemoryStream())
{
Mail.ToStream(stream, new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.Q, Encoding.UTF8), Encoding.UTF8);
stream.Position = 0;//Need to be reset to 0, otherwise nothing will be sent;
Host.SendMessage(stream);
Host.Disconnect();
}
}
else
throw new Exception("These is not a host to send mail or there is not a mail need to be sent.");
}
}

阅读LumiSoft.Net的源代码,可以看到LumiSoft.Net编程严格遵循了RFC(Request For
Comments)定义的协议规范。通过阅读这些源码对于了解RFC和其中关于邮件网络协议规范也是非常有帮助的。如果想查阅RFC文档可以通过这个链接

在上面的代码中MIME_MediaTypes类,MIME_TransferEncodings类和Encoding类(System.Text.Encoding)都是或类似于枚举,设置了邮件内容的编码方式或解析方式,这个几个类从根本上决定了邮件的正常传输和显示。MIME_TransferEncodings类设置了文件传输编码,决定邮件头中的Content-Transfer-Encoding字段的值及其他需要传输编码字段的编码方式(如标题中的多国语言)。MIME_MediaTypes类设置邮件各部分内容的类型,决定邮件中Content-Type字段的值。而Encoding类不用说,决定了charset的值。关于这些设置的具体作用下文还将提到,这里略过。

4、测试

下表是通过网络搜集的各大SMTP服务器的配置情况,可以选择使用这些配置进行测试:





































服务商 SMTP地址 SMTP端口 EnableSsl
gmail smtp.google.com 25, 465 or 587 true
126 smtp.126.com 25 false
163 smtp.126.com 25 false
hotmail smtp.live.com 25 true
sina smtp.sina.com 25 false
sohu smtp.sohu.com 25 false

新建控制台应用程序,测试发送只包含正文的简单邮件:


    class Program
{
static void Main(string[] args)
{
var h1 = new ConfigHost()
{
Server = "smtp.gmail.com",
Port = 465,
Username = "******@gmail.com",
Password = "******",
EnableSsl = true
};
            var m1 = new ConfigMail()
{
Subject = "Test",
Body = "Just a test.",
From = "******@gmail.com",
To = new string[] { "******@gmail.com" },
            };

var agents = new List<ISendMail>() { new UseNetMail(), new UseOpenSmtp(), new UseLumiSoft() };
foreach (var agent in agents)
{
var output = "Send m1 via h1 " + agent.GetType().Name + " ";
Console.WriteLine(output + "start");
try
{
agent.CreateHost(h1);
m1.Subject = output;
agent.CreateMail(m1);
agent.SendMail();
Console.WriteLine(output + "success");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine(output + "end");
Console.WriteLine("-----------------------------------");
}
Console.Read();
}
}

通过gmail发送邮件时,OpenSmtp由于不支持SSL发送失败,NetMail使用587端口能够成功发送,LumiSoft使用465端口能够成功发送。查阅Gmail相关文档,描述说Gmail的465端口使用SSL协议,而587端口使用TLS协议,但587是需要STARTTLS命令支持才能提升为TLS。在命令提示符下测试发现的确需要在发送STARTTLS命令后才能使用TLS协议:


> telnet smtp.gmail.com 587
220 mx.google.com ESMTP o5sm40420786eeg.8 - gsmtp
EHLO g1
250-mx.google.com at your service, [173.231.8.212]
250-SIZE 35882577
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250 CHUNKING
AUTH LOGIN
530 5.7.0 Must issue a STARTTLS command first. o5sm40420786eeg.8 – gsmtpSTARTTLS220
STARTTLS
2.0.0 Ready to start TLS
QUIT

对于TLS与STARTTLS人们经常搞混,这里找到一篇关于它们的解释,请点击这里

因而LumiSoft如果连接gmail服务器时还需明确发送STARTTLS命令,已经发现LumiSoft有相关方法SMTP_Client.StartTLS(),连接gmail相较其他smtp服务器还是较为复杂些。另外一些服务器要求邮件配置中的Username必须与From相一致,需要特别注意。

测试发送带附件和内嵌资源的邮件:


    class Program
{
static void Main(string[] args)
{
var h2 = new ConfigHost()
{
Server = "smtp.163.com",
Port = 25,
Username = "******@163.com",
Password = "******",
EnableSsl = false
};
var m2 = new ConfigMail()
{
Subject = "Test",
Body = "Just a test. <br/><img src=‘cid:" + Convert.ToBase64String(Encoding.Default.GetBytes("Resource.jpg")) + "‘ alt=‘‘/> ",
                From = "******@163.com",
To = new string[] { "******@163.com" },
                Attachments = new string[] { @"E:\Test\SendMail\Attachment.pdf" }, 
                Resources = new string[] { @"E:\Test\SendMail\Resource.jpg" }
};

var agents = new List<ISendMail>() { new UseNetMail(), new UseOpenSmtp(), new UseLumiSoft() };
foreach (var agent in agents)
{
var output = "Send m2 via h2 " + agent.GetType().Name + " ";
Console.WriteLine(output + "start");
try
{
agent.CreateHost(h2);
m2.Subject = output;
agent.CreateMultiMail(m2);
agent.SendMail();
Console.WriteLine(output + "success");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine(output + "end");
Console.WriteLine("-----------------------------------");
}
Console.Read();
}
}

C#邮件发送问题(二)

C#邮件发送问题(一),布布扣,bubuko.com

时间: 2024-10-12 17:15:55

C#邮件发送问题(一)的相关文章

学习笔记之邮件发送篇

用脚本语言发送邮件是系统管理员必备技能 对系统定期检查或者当服务器受到攻击时生成文档和报表. 发布这些文档最快速有效的方法就是发送邮件. python中email模块使得处理邮件变得比较简单 发送邮件主要用到了smtplib和email两个模块,这里首先就两个模块进行一下简单的介绍: 本段摘录于    http://www.cnblogs.com/xiaowuyi/archive/2012/03/17/2404015.html 1.smtplib模块 smtplib.SMTP([host[, p

java-基于JavaMail的Java邮件发送

1.基于JavaMail的Java邮件发送:简单邮件发送 2.基于JavaMail的Java邮件发送:复杂邮件发送

更改邮件发送语言为英语,解决编码为UTF8邮箱注册账号,邮件内容乱码问题

Change email English language, code for UTF8 mailbox registered account, email content garbled. 1. code analysis 乱码分析 通过对中文编码的邮件服务器使用原来的系统(GB2312) The original system used by the mail server encoding for the Chinese code (GB2312) 我使用outlook.com的邮箱(UT

redmine邮件发送功能配置详解

redmine的邮件发送功能还是很有用的. 像项目有更新啦,任务分配啦,都能邮件发送的相关责任人. 我自己在linux服务器上安装并启动了redmine后,邮件一直发送了不了. 查了网上的资料,都是讲修改下配置文件就可以了,他们没错,只是没有讲全. 下面是我整理的一个redmine邮件发送功能设置的一个完整流程. 1. sendmail安装与检查 linux机器上安装的redmine要能发送邮件,先得是本机的sendmail功能是正常的. 查看sendmail进程是否已正常启动: $ ps au

自动化邮件报告平台-邮件发送highchart图表

前段时间参与开发这样的一个系统,负责前端设计开发,使用人员提出需要在邮件发送的时候自动获取这些highchart图表数据,并显示在平台页面上,当发送邮件的时候也把图表附带在邮件中. highchart是一个比较强大的图表组件,这个图表组件以svg方式渲染在网页上,渲染完毕后会在网页中添加了svg元素,可以通过dom 或者jQuery 把svg内容单独抽取出来,此svg元素也能够在网页上直接显示,如下图所示.  但是,在邮箱环境下,这些svg元素不一定能展示在邮件里面,各种邮箱环境不同,在手机端邮

JavaMail 邮件发送之使用qq邮箱

所需jar包:comment-email.jar     mail.jaractivation.jar 一. 配置QQ邮箱的IMAP 进入qq电子邮件点击 设置->账户里开启 SMTP 服务(开启IMAP/SMTP服务)   注意:在启用QQ邮箱的14天之后才能开启此服务 开启之后会得到授权码,此授权码要记住或者保存到文本文件当中发送邮件的时候需要作为验证密码使用. 二.使用JavaMail发送一封简单邮件 的示例代码: public static void main(String[] args

全网备份中邮件发送的报错解决-20160926

第1章 邮件发送不成功解决方法: 1.1故障现象: [[email protected] scripts]# mail -s "Backup results_$(date+%F)" [email protected] </tmp/mail_$(date +%F).txt [[email protected] scripts]# smtp-server: 535 Error: authenticationfailed "/root/dead.letter" 13

用ASP.NET Core 1.0中实现邮件发送功能-阿里云邮件推送篇

在上篇中用MailKit实现了Asp.net core 邮件发送功能,但一直未解决阿里云邮件推送问题,提交工单一开始的回复不尽如人意,比如您的网络问题,您的用户名密码不正确等,但继续沟通下阿里云客户还是很耐心的. 最终结论,是由于MailKit发送了两次EHLO命令,查看了MailKit源码后竟然发现,里面写了硬编码: if (host != "smtp.strato.de" && host != "smtp.sina.com") Ehlo (can

通过邮件发送running process输出最后N行

#!/usr/bin/python #coding: utf-8 import sys import smtplib from email.mime.text import MIMEText import re,commands def lastline():     global pos     while True:         pos = pos - 1         try:             f.seek(pos, 2)             if f.read(1) =

使用Spring的JAVA Mail支持简化邮件发送(转)

闲来无事,翻看<Spring in Action>,发现Spring集成了对JAVA Mail的支持,有点小激动的看了一遍,嗯,话说真的简单了很多. Spring的邮件发送的核心是MailSender接口,在Spring3.0中提供了一个实现类JavaMailSenderImpl,这个类是发送邮件的核心类.可以通过在配置文件中配置使用,当然也可以自己硬编码到代码中(方便起见,下面的演示代码都是硬编码到代码中,省得配置麻烦). Spring提供的邮件发送不仅支持简单邮件的发送.添加附件,而且还可