接触过邮件发送的人,可能对SMTP协议以及对dotnet下面的SMTP类的操作应该不会陌生,但可能不一定了解ESMTP是什么东西,下面我们对它先做一个介绍,然后再详细介绍两种方式在发送嵌入图片(不是附件模式)操作的实现。
介绍SMTP命令和ESMTP过程
SMTP (Simple Mail Transfer Protocol) : 电子邮件从客户机传输到服务器或从某一个服务器传输到另一个服务器使用的传输协议。 SMTP 是请求/响应协议,命令和响应都是基于 ASCII 文本,并以 CR 和 LF 符结束。响应包括一个表示返回状态的三位数字代码。SMTP 在 TCP 协议 25 端口监听连接请求。
什么是 ESMTP
ESMTP (Extended SMTP),顾名思义,扩展 SMTP 就是对标准 SMTP 协议进行的扩展。它与 SMTP 服务的区别仅仅是,使用 SMTP 发信不需要验证用户帐户,而用 ESMTP 发信时, 服务器会要求用户提供用户名和密码以便验证身份。验证之后的邮件发送过程与 SMTP 方式没有两样。
SMTP 命令
SMTP 命令包括:
HELO 向服务器标识用户身份。发送者能欺骗,说谎,但一般情况下服务器都能检测到。
EHLO 向服务器标识用户身份。发送者能欺骗,说谎,但一般情况下服务器都能检测到。
MAIL FROM 命令中指定的地址是发件人地址
RCPT TO 标识单个的邮件接收人;可有多个 RCPT TO;常在 MAIL 命令后面。
DATA 在单个或多个 RCPT 命令后,表示所有的邮件接收人已标识,并初始化数据传输,以 CRLF.CRLF 结束
VRFY 用于验证指定的用户/邮箱是否存在;由于安全方面的原因,服务器常禁止此命令
EXPN 验证给定的邮箱列表是否存在,扩充邮箱列表,也常被禁用
HELP 查询服务器支持什么命令
NOOP 无操作,服务器应响应 OK
RSET 重置会话,当前传输被取消
QUIT 结束会话
以上这些是SMTP和ESMPT的基本知识,特别是这些命令,对我们操作ESMTP协议帮助会比较大的,说白了,ESMTP就是通过Socket流方式和邮件服务器进行交互,基本上目前所有的服务器都会提供对ESMTP协议的支持,SMTP协议在dotnet中实现就是通过调用System.Net.Mail .SmtpClient实现的。
发送嵌入图片或者文件邮件的原理就是,把图片资源提交(和附件类似方式,但是不同),然后通过修改HTML的IMG标识的SRC实现实现内容的相对引用的。
如我们插入本地图片文件的时候,文件的地址是“D:\test.jpg",如果这样发送出去,那么肯定是查看不到的了,我们要将它修改为:<img src=\"cid:test\">
然后,我们在邮件正文中附加相关的文件就可以了,这个有点类似于一种特殊的定位格式。具体的示例代码如下所示。
System.Net.Mail.MailMessage mailMessage = new System.Net.Mail.MailMessage();
mailMessage.To.Add("收件人邮件列表");
mailMessage.CC.Add("抄送人邮件列表");
mailMessage.Subject = subject;
AlternateView htmlBody = AlternateView.CreateAlternateViewFromString(content,null,"text/html");
LinkedResource lrImage = new LinkedResource("test.jpg","image/gif");
lrImage.ContentId = "test";
htmlBody.LinkedResources.Add(lrImage);
mailMessage.AlternateViews.Add(htmlBody);
SmtpClient.Send(mailMessage);
其实对于调用SmtpClient发送嵌入图片文件的操作,网上已经有不少文章介绍了,本人在寻找过程中,发现几篇不错的文章,不敢独享,分享出来大家学习,下面提供几篇介绍这方面知识的文章:
.NET C# 异步发送 要求回执 嵌入图片资源 自定义邮件头 失败通知 html/文本双视图 支持 notes 的邮件
.NET C# 发送邮件内容嵌入图片
C#用于Windows程序的HTML编辑器(推荐,附源码的)
以上都是基于SmtpClient类的实现,没有发现有关ESMTP实现,即通过TCP Scoket进行发送嵌入图片文件的邮件的方法。
其实在文章"c#发送需要smtp认证的邮件"(http://www.legalsoft.com.cn/docs/docs/17/577.html) 中,已经很详细介绍了如何实现使用ESMTP协议与服务器之间的交互了,其中还包括了发送邮件附件,算是比较详细的ESMTP实现了。
其实实现发送ESMTP的嵌入图片,和发送附件方式有点类似了。
1、首先你需要编写一个函数,用来解析发送的HTML内容,遇到有本地图片的,将文件路径替换为Cid:***格式,并将文件写入内存的Stream中备用,示例代码如下所示。
private Hashtable EmbedList = new Hashtable(); //widened scope for MatchEvaluator
{
//Build a symbol table to avoid redundant embedding.
Regex imgRE, linkRE, hrefRE;
MatchCollection imgMatches;
string imgMatchExpression = "(?:img[^>]+src\\s*=\\s*(?:\"(?<1>[^\"]*)\"|(?<1>\\S+))|url\\(['\"](?<1>[^'\"]*)['\"]\\))";
imgRE = new Regex(imgMatchExpression, RegexOptions.IgnoreCase | RegexOptions.Compiled);
string linkMatchExpression = "<\\s*link[^>]+href\\s*=\\s*(?:\"(?<1>[^\"]*)\"|(?<1>\\S+))[^>]*>";
linkRE = new Regex(linkMatchExpression, RegexOptions.IgnoreCase | RegexOptions.Compiled);
//this one's for fixup of relative urls in anchors
string refMatchExpression = "href\\s*=\\s*(?:['\"](?<1>[^\"]*)['\"]|(?<1>\\S+))";
hrefRE = new Regex(refMatchExpression, RegexOptions.IgnoreCase | RegexOptions.Compiled);
imgMatches = imgRE.Matches(rawPayload);
//translation to a Hashtable weeds out redundant references
foreach (Match m in imgMatches)
{
if (!EmbedList.ContainsKey(m.Groups[1].Value))
{
EmbedList.Add(m.Groups[1].Value, Guid.NewGuid());
}
}
//Prepare embedded data
extras.Length = 0;
string contentType;
ArrayList embeddees = new ArrayList(EmbedList.Keys);
foreach (string embeddee in embeddees)
{
contentType = embeddee.Substring(embeddee.LastIndexOf(".") + 1).ToLower();
extras.AppendFormat(boundaryString);
if (contentType.Equals("jpg")) contentType = "jpeg";
switch (contentType)
{
case "jpeg":
case "gif":
case "png":
case "bmp":
extras.AppendFormat("Content-Type: image/{0}; charset=\"iso-8859-1\"\r\n", contentType);
extras.Append("Content-Transfer-Encoding: base64\r\n");
extras.Append("Content-Disposition: inline\r\n");
extras.AppendFormat("Content-ID: <{0}>\r\n\r\n", EmbedList[embeddee]);
extras.Append(GetDataAsBase64(embeddee));
extras.Append("\r\n");
break;
}
}
//Fixups for references to items now embedded
rawPayload = imgRE.Replace(rawPayload, new MatchEvaluator(_fixup));
return rawPayload;
}
private string _fixup(Match m)
{
string replaceThis = m.Groups[1].Value;
string withThis = string.Format("cid:{0}", EmbedList[replaceThis]);
return m.Value.Replace(replaceThis, withThis);
}
然后你在和服务器交互的时候,就可以通过这个函数,获取解析后的HTML文件内容和图片流,对其进行操作即可。
if (Html)
{
SendBufferstr += "Content-Type: text/html;" + enter;
}
else
{
SendBufferstr += "Content-Type: text/plain;" + enter;
}
//编码信息
if (Charset == "")
{
SendBufferstr += " charset=\"iso-8859-1\"" + enter;
}
else
{
SendBufferstr += " charset=\"" + Charset.ToLower() + "\"" + enter;
}
SendBufferstr += "Content-Transfer-Encoding: base64" + enter;
StringBuilder extras = new StringBuilder();
string extrasBoundary = "--" + boundary + enter;
string newBodyHtml = FixupReferences(this.Body, ref extras, extrasBoundary);
SendBufferstr += enter + enter;
SendBufferstr += B64StrLine(Base64Encode(newBodyHtml)) + enter;
SendBufferstr += enter + "--" + boundary1 + "--" + enter + enter;
SendBufferstr += extras.ToString();
下面是一份图片邮件的例子: