nodejs后台郵件服務器原理詳解


一、發送郵件

  單一的node后台其實本身並沒有發送郵件的功能,要想實現發送郵件的效果,還是需要借助一個郵箱來實現郵件的發送。

  流程:前端提出發送需求 —— Node后台收集需要發送的信息 —— 發送給郵箱服務器來進行發送

  在node后台 —— 郵箱后台的這個過程中,遵循了SMTP協議(SMTP(Simple Mail Transfer Protocol)簡單郵件傳輸協議)。這個協議來控制郵件的中轉方式,用於因特網中郵件服務器之間交換郵件。

二、接收郵件

  后台接收郵件其實就是把郵箱已經接受的郵件獲取到node后台。

  流程:node后台請求郵件 —— 郵箱根據請求的情況返回對應的郵件

  在郵箱收到郵件 —— 將信息獲取到后台的過程中,遵循pop3或imap協議。

1、pop3協議

  POP3協議允許電子郵件客戶端下載服務器上的郵件,但是在客戶端的操作(如移動郵件、刪除郵件、標記已讀等),不會反饋到服務器上。比如通過客戶端收取了郵箱中的3封郵件並移動到其他文件夾,郵箱服務器上的這些郵件是沒有同時被移動的。也就是說POP3協議實際上是下載了一份郵件的副本到本地郵件客戶端,而且對本地郵件副本的操作只會影響本地數據。多個郵件客戶端里面的郵件的狀態可能會不一致。

2、imap協議

  IMAP也是提供面向用戶的郵件收取服務。與POP3不同的是,您在電子郵件客戶端收取的郵件仍然保留在服務器上,同時在客戶端上的操作都會反饋到服務器上。

  比如:刪除郵件,標記已讀等,服務器上的郵件也會做相應的動作。換句話說,IMAP把遠程文件夾當成本地文件夾來操作,它們之間類似於雙向同步。這樣的好處是,當你在多個郵件客戶端看見的郵件的狀態是一致的。

三、實現詳解

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 ports  auth: { user: "xxx@qq.com", // generated ethereal user pass: '郵箱的驗證碼'// generated ethereal password  } }); //創建Nodemailer傳輸器  let mailOptions = { from: '"khy" <xxx@qq.com>', // sender address to: 'xxx@qq.com', // list of receivers subject: 'Hello', // Subject line text: 'Hello world?', // plain text body html: '<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 account console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info)); // Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321@example.com> // Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...  }); //sendMail()方法傳遞消息對象

(1)創建Nodemailer傳輸器

  這個部分是設置node后台需要向哪一個郵箱后台,提交郵件請求

nodemailer.createTransport(options[, defaults]) options – 設置鏈接郵箱SMTP數據 host:SMTP地址,從郵件服務提供商獲取 port: SMTP端口號,從郵件服務提供商獲取 secure: true, //465端口為true,其他接口為false  auth: { ​ user: "xxx@qq.com", 請求代理的郵箱地址 ​ pass: 'jfxksdbztpcohjff’郵箱邀請碼,從郵件服務提供商獲取 } defaults – 共享數據對象

  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('授權成功!'); } });

(2)設置消息選項

  這個部分是編輯郵件的內容部分,這一步其實就是建立一個對象,把相關信息作為對象的屬性進行存儲。

  屬性列表:(表中尖括號括起來的內容表示數據類型)
    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作為編碼。附件是以二進制方式傳輸的。

(3)sendMail()方法傳遞消息對象

  使用了這個方法之后,郵件請求就發送到了SMTP服務器中了。

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

  • object就是第二步創建的郵件信息對象對象
  • 回調函數有兩個參數,一個是錯誤信息,一個是發送的信息

三,獲取郵件

  IMAP:https://www.npmjs.com/package/imap,好好看文檔

  在獲取郵件的時候,無非就是以下幾個步驟:

  連接到服務器 —— 打開指定郵箱 —— 篩選(操作)郵件 —— 解析郵件 —— 查看結果

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();

(1)連接郵箱

  構造器:使用指定的配置對象創建並返回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(); //忽略正在傳輸的內容,立即斷開鏈接

(2)打開指定郵箱

  獲取所有郵箱列表: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

(3)篩選(操作)郵件

  搜索郵件: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

(4)抓取郵件內容

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

  source表示UID數組,options表示可以添加的屬性,return

  options屬性:

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也是一個觸發器,當抓取對應郵件下載到本地后觸發

(5)郵件內容解析–使用mailparser

f.on('message', function(msg, seqno) { var mailparser = new MailParser(); //每封郵件添加一個mailparser msg.on('body', function(stream, info) { stream.pipe(mailparser); //將為解析的數據流pipe到mailparser  mailparser.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(); } }); });

  在這次的郵件服務器研究過程中,有一些問題沒有深入分析,只是學習了基本使用方法。比如:在抓取郵件的過程中,涉及到了流式傳輸,這里沒有再深入分析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM