用Markdown写邮件,用Python发邮件

用Markdown写邮件,用Python发邮件

picture 1

平时工作过程中难免要使用邮件,现有的邮件客户端在编辑体验上都不怎么友好,在调整格式时尤其痛苦。以我的有限的人生经验来看,所见即所得的编辑软件往往不如纯文本编辑体验流畅。近些年来,Markdown逐渐成为写作的利器,甚至现在有些出版社也已经接收Markdown手稿。
那么,我们能否使用Markdown来写邮件呢,然后写个Python小脚本去发送邮件呢?

邮件通信的内容使用MIME(Multipurpose Internet Mail Extension)编码,MIME之于邮件客户端类似于html之于浏览器的关系。MIME支持邮件承载多媒体内容,可以把MIME理解为受限的html/css,但是MIME不支持js脚本(安全原因)。

整个程序的工作原理如下图:


picture 2

在自己喜欢的Markdown编辑器下编辑markdown文档/邮件

记得在markdown文档头部添加formatter

From: foo <foo@qq.com>
To: bar <bar@qq.com>
Subject: 测试Markdown邮件
---

配置个人邮箱账号、密码(切记不要在公共电脑存放个人密码,也不要上传到公网)

可以存放到~/.markdown-to-email.json

{
    "username": "your_account", 
    "smtp": "smtp.qq.com:587", 
    "password": "your password/authorization code" 
}

QQ邮箱服务在使用第三方客户端登录时,需要申请授权码。详见:什么是授权码,它又是如何设置?

执行如下的Python代码

完整示例,参考git仓库: https://gitee.com/dearyiming/python-simple-beautiful/tree/master/src/02-email

本地预览

python markdown_email.py -p --md <your markdown file>

注意:mac电脑使用open命令可以直接调用邮箱APP打开预览,其他操作系统可以使用邮箱APP导入邮件(eml文件)去预览

发送邮件

python markdown_email.py -s --md <your markdown file>
#!/usr/bin/env python

'''
Send an multipart email with HTML and plain text alternatives. The message
should be constructed as a plain-text file of the following format:
    
    From: Your Name <your@email.com>
    To: Recipient One <recipient@to.com>
    Subject: Your subject line
    ---
    Markdown content here
The script accepts content from stdin and, by default, prints the raw
generated email content to stdout.
Preview your message on OS X by invoking the script with `-p` or 
`--preview`, and it will open in your default mail client.
To send the message, invoke with `-s` or `--send`. You must have a
JSON file in your home directory named `.markdown-to-email.json`
with the following keys:
    {
        "username": "smtp-username", 
        "smtp": "smtp.gmail.com:587", 
        "password": "your-password" 
    }
Enjoy!
'''


import os
import sys
import json
import argparse
import smtplib
import subprocess

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

import pygments
import markdown2 as markdown

# define arguments
parser = argparse.ArgumentParser(description='Format and send markdown-based emails.',
                                 formatter_class=argparse.RawDescriptionHelpFormatter,
                                 epilog=__doc__)
parser.add_argument('-p', '--preview', action='store_true', 
                    help='Preview the email in Apple Mail.')
parser.add_argument('-s', '--send', action='store_true', 
                    help='Send the email using your configuration.')

parser.add_argument('--md', dest='markdown', type=str, nargs=1, required=True)
args = parser.parse_args()
print(args)
# read in raw message content
raw_content = open(args.markdown[0], 'r').read()

# split out the headers from the markdown message body
header_content, markdown_content = raw_content.split('---', 1)

# render the markdown into HTML 
css = subprocess.check_output(['pygmentize', '-S', 'default', '-f', 'html'])
markdown_content = markdown_content.strip()
html_content = markdown.markdown(markdown_content)
html_content = ''.join([
    '<style type="text/css">',
    str(css),
    '</style>',
    html_content
])

# create a multipart email message
message = MIMEMultipart('alternative')

# parse the headers
headers = {}
for line in header_content.strip().split('\n'):
    if not line.strip(): continue
    key, value = line.split(':', 1)
    headers[key.strip()] = value.strip()

# set the headers
message['To'] = headers.get('To', '')
message['From'] = headers.get('From', '')
message['Subject'] = headers.get('Subject', 'No subject')

# attach the message parts
message.attach(MIMEText(markdown_content, 'plain')) # 如果邮件客户端不支持html,显示Markdown源码
message.attach(MIMEText(html_content, 'html')) # 如果邮件客户端支持html,显示Markdown渲染后的html

if args.send:
    to = message['To'].split(', ')
    
    with open(os.path.expanduser('~/.markdown-to-email.json'), 'rb') as f:
        config = json.loads(f.read())
        server = smtplib.SMTP(config['smtp'])
        server.starttls()
        server.login(config['username'], config['password'])
        server.sendmail(message['From'], to, message.as_string())
        server.quit()
elif args.preview:
    email_dump = '/tmp/preview-with-css.eml'
    open(email_dump, 'w').write(message.as_string())
    os.system('open -a Mail {}'.format(email_dump))
else:
    print(message.as_string())

邮件效果

picture 3

参考信息

  1. 理解邮件传输协议(SMTP、POP3、IMAP、MIME)
  2. cleverdevil/markdown-to-email
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容