Skip to main content

Tedshd's Dev note

使用 IMAP & SMTP 來加強處理 Gmail

Table of Contents

# Intro

Gmail 提供 IMAP & SMTP 的功能

### 內送郵件 (IMAP) 伺服器

  • imap.gmail.com
  • 需要安全資料傳輸層 (SSL):是
  • 通訊埠:993

### 外寄郵件 (SMTP) 伺服器

  • smtp.gmail.com
  • 需要安全資料傳輸層 (SSL):是
  • 需要傳輸層安全性 (TLS):是 (如果可用)
  • 需要驗證:是
  • 安全資料傳輸層 (SSL) 通訊埠:465
  • 傳輸層安全性 (TLS)/STARTTLS 通訊埠:587

Refer - 透過其他電子郵件平台查看 Gmail

簡單的說 IMAP(Internet Message Access Protocol) 可以存取該 mail 的信件

SMTP(Simple Mail Transfer Protocol) 是可以利用該 mail 發信

詳細說明可以參照 wiki

Refer - 網際網路資訊存取協定

Refer - 簡單郵件傳輸協定

另外還有 POP3(Post Office Protocol) 這個和 IMAP 類似的協定

Refer - 郵局協定

主要 POP3 和 IMAP 的差異主要差在 POP3 是把信件內容拉下來, IMAP 不會, IMAP 是即時連線存取, 但是 IMAP 的好處就是即時連線同步, 這樣在多個裝置就可以即時的更新信件的狀態

所以在一些常見的客戶端程式例如 outlook 或是 Mac OS 裡面的郵件都是利用這些協定在處理 email

這邊當然也可以利用支援這些協定的程式語言寫一些功能來處理 email

# 使用情境

因為公司的服務每天會有大量的 feedback mail 寄到設定的 Gmail 信箱

雖然會有實習生協助整理和處理 feedback mail 但是還是很花人力

這年頭最貴的就是人力了

所以公司有個基於 Python 的程式在協助處理 feedback mail

那程式主要做的事情就是簡單的辨識 SPAM 與替 feedback 的內容打上 tag 以便於之後快速處理使用者的 feedback

# How to do?

  1. 先開啟 Gmail 裡面的 IMAP 的功能

  1. 設定用於第三方應用程式驗證的密碼(並非 Google OAuth)

  1. 開始寫程式

# 怎麼開始寫 code?

這邊以 Python 為例

在開始前得先搞懂流程要幹什麼

  1. 連接 IMAP server
  2. 撈信件
  3. 讀取信件主旨(subject) 和信件內容(content) 和確認附件(attachement)

## smaple code

以下使用 Python

### 建立 IMAP server 連去信箱讀取信件

from datetime import datetime, timedelta, date

import imaplib
import email

from email.header import decode_header
# pip install imap-tools
from imap_tools.imap_utf7 import encode, decode

ACCOUNT = '@gmail.com'
PWD = ''

mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login(ACCOUNT, PWD)
status, data = mail.list()
mail.select('inbox')
# 取前一天的時間
today = (date.today() - timedelta(1)).strftime('%d-%b-%Y')
# 找出前一天未讀的信件
typ, data = mail.search(None, '(UNSEEN)', '(SENTSINCE {0})'.format(today))

# 取到 message id list(message id 就是每封信的 id)
ids = data[0]
id_list = ids.split()

for i in id_list:
    typ, data = mail.fetch(i, '(RFC822)')
    if typ == 'OK':
        print('get mail ok')

    for response_part in data:
            if isinstance(response_part, tuple):
                rawMail = response_part[1].decode()
                # 取得信件
                msg = email.message_from_string(rawMail)
                print(msg)

基本上到這裡拿到 msg 就是這封信件的資訊了

就可以取出來做一些處理

例如: 判斷來信者來回信, 判斷內容替信件添加標籤 等等

關於 imap.search 可以如何使用可參考

Refer - Python’s imaplib (Example)

### 回信

幾本上回信也是寄信的一種

寄信的部分就是得用到 SMTP

幾本上的流程就是

  1. 連接 Gmail 的 SMTP server

  2. 寫信

  3. 寄信

  4. 關閉連接

以下為寄信範例

import email
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

smtp_server = smtplib.SMTP_SSL('smtp.gmail.com')

ACCOUNT = '@gmail.com'
PWD = ''

smtp_server.login(ACCOUNT, PWD)
send_mail = MIMEMultipart('mixed')
body = MIMEMultipart('alternative')
body.attach(MIMEText('test send mail', 'plain'))
body.attach(MIMEText('<div>test send mail<div>', 'html'))
send_mail.attach(body)

send_mail['message-id'] = email.utils.make_msgid()
send_mail['subject'] = ''
send_mail['to'] = ''
send_mail['from'] = '@gmail.com'

smtp_server.sendmail('@gmail.com', send_mail['to'], send_mail.as_string())

smtp_server.quit()

需要回信的話就得在發送時指定 references 和指定要回覆給哪封信(每一封信都有獨立的 message id)

所以會先用 imap 取到 mail list 這樣就會有 message id 的資訊了

以下為回信的範例

from datetime import datetime, timedelta, date

import imaplib
import email

from email.header import decode_header
# pip install imap-tools
from imap_tools.imap_utf7 import encode, decode

ACCOUNT = '@gmail.com'
PWD = ''

mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login(ACCOUNT, PWD)
status, data = mail.list()
mail.select('inbox')
# 取前一天的時間
today = (date.today() - timedelta(1)).strftime('%d-%b-%Y')
# 找出前一天未讀的信件
typ, data = mail.search(None, '(UNSEEN)', '(SENTSINCE {0})'.format(today))

# 取到 message id list(message id 就是每封信的 id)
ids = data[0]
id_list = ids.split()

for i in id_list:
    typ, data = mail.fetch(i, '(RFC822)')
    if typ == 'OK':
        print('get mail ok')

    for response_part in data:
            if isinstance(response_part, tuple):
                raw_mail = response_part[1].decode()
                # 取得信件
                msg = email.message_from_string(raw_mail)
                print(msg)
                var_subject = msg['subject']
                subject, encoding = decode_header(var_subject)[0]

                # add origin content
                for part in msg.walk():
                    if (part.get('Content-Disposition') and part.get('Content-Disposition').startswith("attachment")):
                        part.set_type('text/plain')
                        part.set_payload('Attachment removed: %s (%s, %d bytes)' % (part.get_filename(), part.get_content_type(), len(part.get_payload(decode=True))))
                        del part['Content-Disposition']
                        del part['Content-Transfer-Encoding']

                reply_mail = MIMEMultipart('mixed')
                body = MIMEMultipart('alternative')
                body.attach(MIMEText('reply mail', 'plain'))
                body.attach(MIMEText('<div>reply mail</div>', 'html'))
                reply_mail.attach(body)

                reply_mail['message-id'] = email.utils.make_msgid()
                reply_mail['in-reply-to'] = msg['message-id']
                reply_mail['references'] = msg['message-id']
                reply_mail['subject'] = 're: ' + msg['subject']
                reply_mail['to'] = msg['reply-to'] or msg['from']
                reply_mail['from'] = '@gmail.com'

                smtp_server.sendmail('@gmail.com', new['to'], new.as_string())