一、使用场景

在项目整体的实现中,我们可能需要使用邮箱来提供一些服务,或是发送一些重要的信息。这时我们的后台就需要使用邮箱服务器来提供服务。常见的场景有:

  • 邮箱注册时的验证
  • 邮箱密码找回
  • 后台崩溃并邮件报告管理员
  • 接收一些用户的邮件反馈

二、邮件服务器原理

1.发送邮件

单一的node后台其实本身并没有发送邮件的功能,要想实现发送邮件的效果,还是需要借助一个邮箱来实现邮件的发送。

流程:前端提出发送需求---->Node后台收集需要发送的信息---->发送给邮箱服务器来进行发送

  • node后台---->邮箱后台的这个过程中,遵循了SMTP协议。这个协议来控制邮件的中转方式,用于因特网中邮件服务器之间交换邮件。

SMTP(Simple Mail Transfer Protocol)简单邮件传输协议

2.接收邮件

后台接收邮件其实就是把邮箱已经接受的邮件获取到node后台。

流程:node后台请求邮件---->邮箱根据请求的情况返回对应的邮件

  • 邮箱收到邮件---->将信息获取到后台的过程中,遵循pop3或imap协议。

pop3协议

POP3协议允许电子邮件客户端下载服务器上的邮件,但是在客户端的操作(如移动邮件、删除邮件、标记已读等),不会反馈到服务器上。

比如通过客户端收取了邮箱中的3封邮件并移动到其他文件夹,邮箱服务器上的这些邮件是没有同时被移动的。也就是说POP3协议实际上是下载了一份邮件的副本到本地邮件客户端,而且对本地邮件副本的操作只会影响本地数据。多个邮件客户端里面的邮件的状态可能会不一致。

imap协议

IMAP也是提供面向用户的邮件收取服务。与POP3不同的是,您在电子邮件客户端收取的邮件仍然保留在服务器上,同时在客户端上的操作都会反馈到服务器上。

比如:删除邮件,标记已读等,服务器上的邮件也会做相应的动作。换句话说,IMAP把远程文件夹当成本地文件夹来操作,它们之间类似于双向同步。这样的好处是,当你在多个邮件客户端看见的邮件的状态是一致的。

POP3(Post Office Protocol 3)邮局协议第三版本

IMAP(Internet Mail Access Protocol)internet消息访问协议。

三、实现详解

1.第三方模块

在node的生态中,实现这样的功能有很多的第三方模块。比如:

  • nodemailer
  • node-red
  • node-imap

大家可以在npm上搜索并查看文档进行开发。

下面我们使用nodemailer、imap这两个模块进行讲解,使用的邮箱是qq邮箱。

2.发送邮件(需要使用nodemailer模块)

一共分为三步:

  1. 使用SMTP或其他传输机制创建Nodemailer传输器
  2. 设置消息选项(谁向谁发送消息)
  3. 使用先前创建的传输程序的sendMail()方法传递消息对象
let transporter = nodemailer.createTransport({host: 'smtp.qq.com',port: 465,secure: true, // true for 465, false for other portsauth: {user: "xxx@qq.com", // generated ethereal userpass: '邮箱的验证码'// generated ethereal password}});
//创建Nodemailer传输器let mailOptions = {from: '"khy" <xxx@qq.com>', // sender addressto: 'xxx@qq.com', // list of receiverssubject: 'Hello', // Subject linetext: 'Hello world?', // plain text bodyhtml: '<b>Hello world?</b>' // html body};
//设置消息选项transporter.sendMail(mailOptions, (error, info) => {if (error) {return console.log(error);}console.log('Message sent: %s', info.messageId);// Preview only available when sending through an Ethereal accountconsole.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));// Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321@example.com>// Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...});
//sendMail()方法传递消息对象

a.创建Nodemailer传输器

  • 这个部分是设置node后台需要向哪一个邮箱后台,提交邮件请求。

1、nodemailer.createTransport(options[, defaults])

  • options – 设置链接邮箱SMTP数据

    • host:SMTP地址,从邮件服务提供商获取

    • port: SMTP端口号,从邮件服务提供商获取

    • secure: true, //465端口为true,其他接口为false

    • auth: {
      ​ user: "xxx@qq.com", 请求代理的邮箱地址
      ​ pass: 'jfxksdbztpcohjff’邮箱邀请码,从邮件服务提供商获取

      }

  • defaults – 共享数据对象

2、transporter.verify(callback);
这个是后台验证是否授权成功,一般用于后台链接测试,实际项目中,不需要写!

let transporter = nodemailer.createTransport({host: 'smtp.qq.com',port: 465,secure: true, auth: {user: "xxx@qq.com", pass: '邮箱的验证码'}
});
transporter.verify(function(error, success) {if (error) {console.log(error);} else {console.log('授权成功!');}
});

b.设置消息选项

  • 这个部分是编辑邮件的内容部分

我仔细研究翻译了官方的文档,做了一些测试,得到了比较全面的使用方法。这一步其实就是建立一个对象,把相关信息作为对象的属性进行存储。

属性列表:(表中尖括号括起来的内容表示数据类型)

  • from - 邮件发出的地址

  • to - 邮件送到的地址

  • cc - 抄送的地址

  • bcc - 加密抄送的地址

  • subject - 邮件的标题

  • text - 发送的文本 , ,

  • html - 发送html页, ,

  • attachments - 添加附件对应的属性:

    • filename - 文件名
    • content - 传输内容, , 可以将数据导入文件,作为附件发送。
    • path - 本地路径如果希望流式传输文件而不是包含该文件,则使用该文件的路径(对于较大的附件更好)
    • href – 网络数据信息路径。
    • contentType - 规定附件的格式,如果未设置,将从文件名生成
    • contentDisposition - optional content disposition type for the attachment, defaults to ‘attachment’
    • cid - optional content id for using inline images in HTML message source
    • encoding - If set and content is string, then encodes the content to a Buffer using the specified encoding. Example values: ‘base64’, ‘hex’, ‘binary’ etc. Useful if you want to use binary attachments in a JSON formatted email object.
    • headers - custom headers for the attachment node. Same usage as with message headers
    • raw - is an optional special value that overrides entire contents of current mime node including mime headers. Useful if you want to prepare node contents yourself

    上边部分不太常用的的内容没有翻译,一般在邮件发送时也不需要编写,英文内容来源于nodemailer官方文档。

//address实例---符合address格式的例子
1.字符串:'foobar@example.com'
2.字符串嵌套:'"Майлер, Ноде" <foobar@example.com>'
3.对象:{name: 'Майлер, Ноде',address: 'foobar@example.com'
}
4.数组['"Ноде Майлер" <bar@example.com>,'"Name, User" <bar@example.com>'
]
5.混合数组['foobar@example.com',{name: 'Майлер, Ноде',address: 'foobar@example.com'}
]//attachments实例
attachments: [{   //字符串生成附件filename: 'text1.txt',content: 'hello world!'},{   // 字符流生成附件filename: 'text2.txt',content: new Buffer('hello world!','utf-8')},{   // 磁盘文件生成附件filename: 'text3.txt',path: '/path/to/file.txt' },{  //文件名及类型会自动从路径获取path: '/path/to/file.txt'},{   // 字符流生成附件filename: 'text4.txt',content: fs.createReadStream('file.txt')},{   // 为附件定义自定义内容类型filename: 'text.bin',content: 'hello world!',contentType: 'text/plain'},{   // 使用URL添加附件filename: 'license.txt',path: 'https://raw.github.com/nodemailer/nodemailer/master/LICENSE'}]

注意:

  • 在属性中“from”必填,且必须是Nodemailer传输器中已经授权的邮箱。否则会出现错误
  • 在属性中“to,cc,bcc”三者都表示发送到邮箱的地址。可以同时选则多个属性,每个属性也可以同时添加多个目标地址,但是不能三个属性一个收件地址都不填。
  • subject,text,html,attachments可以为空。当他们为空时会发送空邮件。
  • 磁盘文件只能使用path路径,网络连接可以使用path或href。
  • 附件名称可以修改,可以与文件名称不一致。
  • 在设置对象中有两个属性分别是text、和html。这两个对象都是邮件需要发送的内容,当html为空时,邮件会发送空的文本。当html不为空时,html会覆盖text内容(邮件主体呈现出html内容,而不显示text内容)。
  • 所有文本字段(电子邮件地址、明文主体、html主体、附件文件名)都使用UTF-8作为编码。附件是以二进制方式传输的。

c.sendMail()方法传递消息对象

使用了这个方法之后,邮件请求就发送到了SMTP服务器中了。

nodemailer.sendMail(object,callback(err,info))

  • object就是第二步创建的邮件信息对象对象
  • 回调函数有两个参数,一个是错误信息,一个是发送的信息

3.获取邮件(使用imap和nodemailer模块)

在获取邮件的时候,无非就是以下几个步骤:

连接到服务器—>打开指定邮箱---->筛选(操作)邮件---->解析邮件---->查看结果

var Imap = require('imap'),inspect = require('util').inspect;var imap = new Imap({user: 'mygmailname@gmail.com',password: 'mygmailpassword',host: 'imap.gmail.com',port: 993,tls: true
});function openInbox(cb) {imap.openBox('INBOX', true, cb);
}imap.once('ready', function() {openInbox(function(err, box) {if (err) throw err;var f = imap.seq.fetch('1:3', {bodies: 'HEADER.FIELDS (FROM TO SUBJECT DATE)',struct: true});f.on('message', function(msg, seqno) {console.log('Message #%d', seqno);var prefix = '(#' + seqno + ') ';msg.on('body', function(stream, info) {var buffer = '';stream.on('data', function(chunk) {buffer += chunk.toString('utf8');});stream.once('end', function() {console.log(prefix + 'Parsed header: %s', inspect(Imap.parseHeader(buffer)));});});msg.once('attributes', function(attrs) {console.log(prefix + 'Attributes: %s', inspect(attrs, false, 8));});msg.once('end', function() {console.log(prefix + 'Finished');});});f.once('error', function(err) {console.log('Fetch error: ' + err);});f.once('end', function() {console.log('Done fetching all messages!');imap.end();});});
});imap.once('error', function(err) {console.log(err);
});imap.once('end', function() {console.log('Connection ended');
});imap.connect();

a.连接邮箱

  • 构造器:使用指定的配置对象创建并返回Connection的新实例。设置需要连接到哪一个服务器
var imap = new Imap({user: 'xxx@qq.com', //你的邮箱账号password: 'xxx', //你的邮箱邀请码host: 'imap.qq.com', //邮箱服务器的主机地址port: 993, //邮箱服务器的端口地址tls: true, //使用安全传输协议tlsOptions: { rejectUnauthorized: false } //禁用对证书有效性的检查connTimeout: 10000,//链接超时等待数,默认10000毫秒 authTimeout: 5000,//身份验证的毫秒数
});
  • 与服务器构建链接:connect()
imap.connect();
//链接邮箱,并身份验证
  • 与服务器断开链接:end(),destroy()
imap.end();
//当所有的请求结束后,断开链接
imap.destroy();
//忽略正在传输的内容,立即断开链接

b.打开指定邮箱

  • 获取所有邮箱列表:getBoxes
imap.getBoxes(function(err,box){//box就是邮箱的列表//box具体内容如下://INBOX:收件箱//Sent Messages:发送的邮件//Drafts:草稿箱//Deleted:删除邮件//Junk:垃圾邮件
})
  • 打开邮箱:openBox(< string >mailboxName[, < boolean >openReadOnly=false], < function >callback)
imap.openBox('INBOX',true,function(err,box){//打开邮箱后的操作
})
  • 关闭邮箱:closeBox([< boolean >autoExpunge=true, ]< function >callback)
imap.closeBox(true,function(err){//如果关闭失败,触发回调函数
})

​ 注意:这里的第一个参数可选,默认是true。如果autoExpunge为真,则在当前打开的邮箱中标记为Deleted的任何消息都将被删除,如果邮箱未在只读模式下打开。如果autoExpunge为false,则断开连接或打开另一个邮箱,标记为Deleted的消息将不会从当前打开的邮箱中删除。

  • addBox,delBox,renameBox等一些不常用的方法参考npm-imap文档:https://www.npmjs.com/package/imap

c.筛选(操作)邮件

  • 搜索邮件:search(< array >criteria, < function >callback)

    callback函数有两个参数(err,list)list是符合要求的UID数组

    UID是邮箱用来标识你这个账户的每一封邮件的编号

criteria:设置筛选条件

  • UID:通过邮箱标识符查找
//常用实例
imap.search( [['UID', '491']],function(err,list){console.log(box);
})//搜索UID为491的邮件
imap.search( [['UID', '430:450']],function(err,list){console.log(box);
})//搜索UID为430到450的邮件
imap.search( [['UID', '430:*']],function(err,list){console.log(box);
})//搜索UID为430及以上的邮件
imap.search( [['UID', '430:450','491']],function(err,list){console.log(box);
})//搜索UID为430到450或491的邮件
  • ALL:查找所有邮件
  • UNSEEN:查找未读信息
  • UNSEEN:查找已读信息
  • ANSWERED:已经回复后的消息
imap.search( ['ALL'],function(err,list){console.log(box);
})//搜索所有邮件
//其他功能替换ALL即可
  • ‘BEFORE’ - 在指定日期之前的所有邮件
  • ‘ON’ - 在指定日期当天的邮件
  • ‘SINCE’ - 在指定日期及之后的邮件
imap.search( [['BEFORE','2018/12/7']],function(err,list){console.log(box);
})//搜索所有邮件
//第二个参数为,js可以解析的日期字符串或日期对象
  • ‘LARGER’ :邮件的大小大于指定字节数。
  • ‘SMALLER’:邮件的 大小小于指定字节数。
imap.search( [['LARGER',1280]],function(err,list){console.log(box);
})//搜索所有邮件
//第二个参数为int类型的数字
  • 数组嵌套表示且
  • 数组结合‘OR’表示或
 [ 'UNSEEN', ['SINCE', 'April 20, 2010'] ][ ['OR', 'UNSEEN', ['SINCE', 'April 20, 2010'] ] ]

注意:

  • 国内的邮件服务商,并没有对所有的search语句进行实现。建议大家使用Gmail。
  • 以上只列出了一些常用方法,全部方法请参考:https://www.npmjs.com/package/imap

d.抓取邮件内容

  • fetch(< MessageSource >source, < object >options)

source表示UID数组,options表示可以添加的属性,return

options属性:

  • bodies 选择抓取的部分

    • ‘HEADER’ - 头部信息
    • ‘TEXT’ - 邮件主体
    • ‘’ -全部信息(头部+主体)
    • 其他属性参考:https://www.npmjs.com/package/imap
var f = imap.fetch([491,496,493],{bodies:''});
f.on('message',function(msg, seqno){//msg是抓取对应邮件的事件触发器//seqno是邮件在邮箱的编号(不是UID)msg.on('body',function(stream,info){//stream表示可读流,是邮件内容的流//info邮件的基础信息,包括大小编号})//当对应邮件接收流触发msg.on('end',function(){})//当对应邮件所有内容接收完后触发
})
//抓取完一封邮件后触发
f.once('error',function(err){})
//抓去错误后触发
f.once('end',function(){})
//所有邮件抓取结束后触发

注意:

  • on,once都是添加事件,只不过once为一次性事件,on为多次事件

  • f本身就是一个事件的触发器,每抓取一封邮件,message的回调函数会执行,生成对应的那一封邮件的触发器msg

  • msg也是一个触发器,当抓取对应邮件下载到本地后触发

e.邮件内容解析–使用mailparser

f.on('message', function(msg, seqno) {var mailparser = new MailParser();//每封邮件添加一个mailparsermsg.on('body', function(stream, info) {     stream.pipe(mailparser);//将为解析的数据流pipe到mailparsermailparser.on("headers", function(headers) {//headers是Map类型console.log("邮件主题: " + headers.get('subject'));console.log("发件人: " + headers.get('from').text);console.log("收件人: " + headers.get('to').text);});//当邮件头部流全部传入mailparser后触发mailparser.on("data", function(data) {//data是对象if (data.type === 'text') {console.log("邮件内容: " + data.html);}if (data.type === 'attachment') {console.log("附件名称:"+data.filename);data.content.pipe(fs.createWriteStream(data.filename)); //保存附件到当前目录下data.release();}});
});

四、总结

在这次的邮件服务器研究过程中,有一些问题没有深入分析,只是学习了基本使用方法。比如:在抓取邮件的过程中,涉及到了流式传输,这里没有再深入分析。

这一篇文件主要由官方文档学习所得,其中有些部分是我自己理解并试验得到的。如果哪些部分有误,请大家帮忙指出。

Node后台邮件服务器相关推荐

  1. CentOS4.4下架设简单的邮件服务器笔记

    1.安装OS说明: 默认情况下安装的包选择:Mail,Gcc,Editor组件及可!如果安装的图形界面的话,要修改启动项,只要修改: [root@extmail ~]# cd /etc [root@e ...

  2. 每日情话之跟着大佬学撩妹,哦不node邮件服务器

    node-mail-service 基于node的邮件服务 定时每日给女朋友们发送情话嘿嘿嘿 就是照着大佬学习的 Github项目地址 启动服务 git clone 本项目 yarn install ...

  3. node 邮箱服务器,Node.js 搭建邮件服务器

    Node.js 搭建邮件服务器 servervar smtp = require('smtp-protocol'); var server = smtp.createServer(function ( ...

  4. linux+postfix+extmail+dovecot搭建邮件服务器

    一.我们可以重新搭建服务器,也可以利用我前面的搭建方法编译安装,地址: http://wangzan18.blog.51cto.com/8021085/1605480,本次我们使用yum的方法来安装h ...

  5. 不是所有邮件服务器都叫智慧邮件系统

    2019独角兽企业重金招聘Python工程师标准>>> 随着TurboMail邮件系统于2012年在行业内首次推出智慧邮件系统V5.0版本后,其他品牌也陆续跟进智慧邮件系统的概念,刹 ...

  6. 邮件服务器软件EwoMail 1.05 发布

    2019独角兽企业重金招聘Python工程师标准>>> EwoMail 1.05 发布了.EwoMail 是基于 Linux 的开源邮件服务器软件,集成了众多优秀稳定的组件,是一个快 ...

  7. 自建邮件服务器给企业带来的商业价值

    2019独角兽企业重金招聘Python工程师标准>>> 自建邮件服务器给企业带来的商业价值 随着电子邮件系统应用多样化的日益升值,尽管lj邮件让人深恶痛绝,电子邮件还是成为了企业所必 ...

  8. 转--Linux邮件服务器软件比较

    Linux邮件服务器软件比较 出处:www.5dmail.net 作者:5dmail  几年以前,Linux环境下可以选择 的可以免费邮件服务器软件只有Sendmail,但是由于Sendmail的缺陷 ...

  9. 利用sendmail搭建邮件服务器

    人们在互联网上最常使用的就是电子邮件了,很多企业用户也经常使用免费的电子邮件系统.今天我就给大家介绍一种在Red Hat Linux 9.0环境下运行的邮件服务器软件Sendmail.Sendmail ...

最新文章

  1. Spring MVC-06循序渐进之Converter和Formatter
  2. hdu 1166 敌兵布阵(线段树之 单点更新+区间求和)
  3. Mybatis+MySQL动态分页查询数据经典案例(含代码以及测试)
  4. getexternalfilesdir 相册_音乐相册(电子相册制作)V5.2 安卓最新版
  5. 事关每个程序员的职业规划与履历
  6. Science报道新研究:同行评审后的研究仅比预印本研究质量提高4%
  7. .net 发送html邮件,c#利用system.net发送html格式邮件
  8. mysql用shell建100多字段表并导入
  9. 我可以在CSS中使用onclick效果吗?
  10. 黑客逆向破解基础-1:壳、加壳和脱壳分别是什么?加壳的解压原理介绍。
  11. 【云游戏】云游戏的架构设计和技术实现
  12. oracle标准成本的维护,Oracle标准成本计算和平均成本计算比较
  13. 智力题(猜凶手,确定比赛名次)
  14. 博后招募 | 浙江大学陈华钧教授招聘知识图谱等方向博后及算法工程师
  15. 红米7 自编译不完美 twrp 可root手机
  16. 人工神经网络及其应用,人工神经网络的实现
  17. 导带电子浓度和价带空穴浓度
  18. 每天学一点flash(78) flash cs5.5 加载 jpeg-xr 格式
  19. meterpreter + 键盘记录
  20. 【转】Principles of User Interface Design

热门文章

  1. uestc1135邱老师看电影【概率dp】
  2. RIP路由项欺骗攻击实验
  3. java 二维码生成及其标签打印
  4. docker之卷10
  5. 【HTML作业】HTML设计--电影网站,影视网站
  6. 2022年秋,工程伦理期末考试答案(仅供参考)
  7. 520被女朋友三番两次拉黑后,我用 Python 写了个“舔狗”必备神器
  8. 关于线性代数:方程组同解
  9. Prometheus使用cAdvisor监控Docker容器指标
  10. office365服务器没有响应,修复:由于长时间运行的脚本,Office 365没有响应