見出し画像

Pythonでやってみた11:メールリストからメールを一斉送信(Outlook)

概要

 仕事柄複数メーカーに発注依頼しておりますが、客先都合で納期変更になったり、納入先詳細を別途連絡するなどで10社くらいに毎回メールを送付する必要が発生したため、「メーリングリストから一括でメール送付」するスクリプトをPythonで作成しました。
 スクリプトおよびフォルダ構成は下記の通りです。

【スクリプトの構成】
1.Excelでメーリングリストを作成
2.メモ帳で送付したい本文を作成
3.Pandasでメーリングリストを取得・前処理/メモ帳の本文情報取得
4.For文でメーリングリストの各情報抽出
5.smtplibを使用してメールを送付

1.Excelでメーリングリスト作成

 送付する件名、担当者、宛先(TO)、CC、対応(処理させるかどうか)をExcelでリスト化しており、注意点は下記の通りです。

【注意点】
●コード側で担当者に継承(様)をつける仕様にしたため担当者名は継承なし
●TOは入力必須(無い場合はコード側で処理しないようにする)
●CCを複数人つける場合はセミコロン(;)で区切る。
 ー>セミコロン後は改行は不要:改行があるとBCCで送付されてしまう

 下記がメーリングリストのサンプルです。前処理の効果が分かるように5,6行目に欠損値があるデータも追加しました。

 なおリスト化作業は手動で実施したためかなり時間がかかりました。

2.メモ帳で送付したい本文を作成

 次にメモ帳にメール本文を記載してファイルを保存します。宛名の”担当者様”に関して、”担当者”の部分を前章メーリングリストの「担当者名」に置換する予定であり、”様”が残る仕様になってます。
(継承の重複より継承漏れの方を防止したいため)

[メール本文.txt]
担当者様

お世話になります。KIYOです。

標記案件についてPJ納期を7月10日に変更したいため、
お手数をおかけしますが調整の方よろしくお願いいたします。

------------------------------------------------
署名欄
note

KIYO
-----------------------------------------------

3.リスト・メモ帳の情報抽出/前処理

3-1.前処理の全コード

 本章の完成コードは下記の通りです。大きく分けて①必要ライブラリのインポート、②メール本文用のテキスト情報取得、③メーリングリストから宛先に値がある行だけ取得 しています。

[IN]
import glob
import os
import re
import pandas as pd

import smtplib, ssl, os
from email.mime.text import MIMEText
import datetime
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.base import MIMEBase
from email import encoders

#ファイルパス
path_text = 'メール本文.txt'
path_maillist = 'メール送付.xlsx'

#メール本文を取得
with open(path_text, 'r', encoding='utf-8') as f:
    text = f.read()
    
df = pd.read_excel(path_maillist)
df = df.dropna(subset=['TO']) #宛先に値がない行は削除
# df = df[df['対応'] == 'Not Done'] #対応が未完の物だけ抽出する場合
df

[OUT]

3-2.メモ帳の本文取得:with open()

 ファイル情報取得するにはPythonの組み込み関数であるopen()を使用します。メモ帳はutf-8で作成したためencoding引数にutf-8を渡しました。
 結果のとおり、現状では宛名は”担当者様”のままです。

[IN]
path_text = 'メール本文.txt'

#メール本文を取得
with open(path_text, 'r', encoding='utf-8') as f:
    text = f.read()
    
print(text)

[OUT]
担当者様

お世話になります。KIYOです。

標記案件についてPJ納期を710日に変更したいため、
お手数をおかけしますが調整の方よろしくお願いいたします。

------------------------------------------------
署名欄
note

KIYO
-----------------------------------------------

3-3.メーリングリストの取得:pandas

 メーリングリストは表形式で使用できるようにpd.read_excel()でデータ取得後にdropna()でTOカラムに値がない行は削除しました。

[IN]
df = pd.read_excel(path_maillist)
df = df.dropna(subset=['TO']) #宛先に値がない行は削除

df
[OUT]

 条件抽出を追加することで「対応がNot Done」の行だけ抽出できます。

[IN]
df = pd.read_excel(path_maillist)
df = df.dropna(subset=['TO']) #宛先に値がない行は削除
df = df[df['対応'] == 'Not Done'] #対応が未完の物だけ抽出する場合

df
[OUT]

 今回は送付漏れがないかの確認もしたいため、完成コードで条件抽出はコメントアウトにしております。

4.For分でメーリングリストの情報抽出

 本章ではsmtplibに渡す値をメーリングリストから抽出していきます。前処理も含めて処理ポイントは下記の通りです。

【コードのポイント】
●DataFrameのデータをfor文で抽出するためdf.itterrows()を使用する。
 ー>出力されるデータはSeries型
 ー>DataFrame型のFor文は処理が遅いがメール送付用なら問題なしとする
●read_excel()で取得したする場合Excelのセルが空だとfloat型で認識される
 ー>CCは空になることもあるためif分で空文字に変換
●件名はメーリングリストの件名+必要な情報を追加するために文字列操作で編集した
●reライブラリを使用して宛名の"担当者"をメーリングリストのデータに置換
●処理が終わったらDataFrameの”対応カラム”を"Not Done"->"Done"に変更
 ー>Jupyterでブロックを分ける(毎回の処理でdfが初期化されない)のであれば、2回目以降以降はメール処理が実行されない。
 ー>Excelの元ファイルが更新されているわけではないため、再起動して最初から処理すると再度メール送信が可能となる。よってExcelファイルは手動で更新("Not Done"->"Done")が必要

[IN]
for idx, data in df.iterrows():
    _subject = data['件名']
    name_To = data['担当者名']
    To_send = data['TO']
    CC_send = data['CC']
    #CCが空の時は型式がfloatになるため空文字を追加
    if pd.isna(CC_send):
        CC_send = ''
    finished = data['対応']
    
    subject = '【依頼】' + '納期変更_' + _subject + '_Xプロジェクト' #件名の作成
    maintext = re.sub('担当者', name_To, text) #宛名を置換

    print(subject)
    print(maintext)
    
    #対応がNot Doneの場合は処理
    if finished == 'Not Done':
        df.loc[idx]['対応'] = 'Done' #Not Done->Doneに変更※元ファイルは変更無し

df


[OUT]
【依頼】納期変更_ABC-製品X_Xプロジェクト
担当A様

お世話になります。KIYOです。

標記案件についてPJ納期を710日に変更したいため、
お手数をおかけしますが調整の方よろしくお願いいたします。

------------------------------------------------
署名欄
note

KIYO
-----------------------------------------------
【依頼】納期変更_XYZ-製品D_Xプロジェクト
担当B様

お世話になります。KIYOです。

標記案件についてPJ納期を710日に変更したいため、
お手数をおかけしますが調整の方よろしくお願いいたします。

------------------------------------------------
署名欄
note

KIYO
-----------------------------------------------
【依頼】納期変更_DD-製品トライアル_Xプロジェクト
担当C様

お世話になります。KIYOです。

標記案件についてPJ納期を710日に変更したいため、
お手数をおかけしますが調整の方よろしくお願いいたします。

------------------------------------------------
署名欄
note

KIYO
-----------------------------------------------

5.smtplibでメール送付

 最後にsmtplibライブラリを使用してメールを送付します。なお使用するソフトはOutlookで作成しております。

5-1.smtplib関数の作成

 メール送付用の関数をsmtplibで作成しました。流れは下記の通りです。

【messageinfo()関数】
①インスタンス化
②必要情報(件名, FROM, TO, CC, 本文)をインスタンスに渡す
③必要であれば添付ファイルを追加(下記ではコメントアウト)

【send_outlook_mail(msg)関数】
①サーバーとポートを設定(Outlookに必要な情報を追加)
②サーバーへの接続処理
③メッセージ情報を添付して送付

[IN]
account = 'KIYO@outlook' #自分のメールアドレス
password = 'note123456789' #メールのパスワード


def messageinfo(subject:str, To_send:str, CC_send:str, text:str):
   #メールデータを作成
   msg = MIMEMultipart()
   msg['Subject'] = subject #件名
   msg['To'] = To_send #宛先
   msg['From'] = account #差出人
   msg['Cc'] = CC_send #CC

   #テキストを追加
   bodytext = text #メモ帳の中身を記載
   txt = MIMEText(bodytext)
   msg.attach(txt)

   #Excelを添付する場合に使用
   # path_attach  = path_maillist
   # attachment = MIMEBase('application', 'vnd.ms-excel')
   # with open(path_attach, 'rb') as fp:
   #    attachment.set_payload(fp.read())

   # encoders.encode_base64(attachment)
   # attachment.add_header("Content-Disposition",
   # "attachment", filename=os.path.basename(path_attach))
   # msg.attach(attachment)
   return msg

def send_outlook_mail(msg):
   server = smtplib.SMTP('smtp.office365.com', 587)
   server.ehlo()
   server.starttls()
   server.ehlo()
   server.login(
       account,
       password
   )
   server.send_message(msg)

5-2.メール処理

 5-1節の関数と4章のコードを使用してメールを送付します。
 ”input()"は実行後に手動で値を入力する関数でありメール誤送信防止用として追加しました。下記コードでは実行後に"123"と入力すれば処理が開始してメールが送付されます。
 なお送付前の確認をしたい場合は「messageinfo」と「send_outlook_mail」をコメントアウトしておけば実行してもメール送付はされません。

[IN]
#メールを送付
sendpass = input() #誤送信防止:手動でパスワードを入力※出力:str
print('メール送信用のパスワードを入力してください。')

if sendpass=='123':
   for idx, data in df.iterrows():
      _subject = data['件名']
      name_To = data['担当者名']
      To_send = data['TO']
      CC_send = data['CC']
      #CCが空の時は型式がfloatになるため空文字を追加
      if pd.isna(CC_send):
         CC_send = ''
      finished = data['対応']
      
      subject = '【依頼】' + '納期変更_' + _subject + '_Xプロジェクト' #件名の作成
      maintext = re.sub('担当者', name_To, text) #宛名を置換

      #対応がNot Doneの場合は処理
      if finished == 'Not Done':
         df.loc[idx]['対応'] = 'Done' #Not Done->Doneに変更※元ファイルは変更無し
         
         msg = messageinfo(subject, To_send, CC_send, text=maintext) #メール送信情報作成
         send_outlook_mail(msg) #メール送付
      
display(df)

6.完成コード

 完成コードは下記の通りです。Jupyterで使用することを想定してブロックを2つに分けました。

【注意点】
●使用時は”account”と”password”を自分のoutlookに変更したらメール送付できます。
●誤送信防止のため手動でパスワード入力(pass=123)を追加してます。
●メール送付をしてもExcel側の対応カラムは変更されません。誤送信を避けるためコード実行後に手動でExcelの対応列を("Not Done"->"Done")へ変更することを推奨します。
●コメントアウトしている添付用コードはExcel専用です。PDFを添付する場合はコードの修正が必要になります。
●事前テストしたい方は「捨てアド」の使用がオススメです。

[IN]
import glob
import os
import re
import pandas as pd

import smtplib, ssl, os
from email.mime.text import MIMEText
import datetime
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.base import MIMEBase
from email import encoders

#ファイルパス
path_text = 'メール本文.txt'
path_maillist = 'メール送付.xlsx'

#メール本文を取得
with open(path_text, 'r', encoding='utf-8') as f:
    text = f.read()
    
df = pd.read_excel(path_maillist)
df = df.dropna(subset=['TO']) #宛先に値がない行は削除
# df = df[df['対応'] == 'Not Done'] #対応が未完の物だけ抽出する場合
df
[IN]
account = 'KIYO@outlook' #自分のメールアドレス
password = 'note123456789' #メールのパスワード


def messageinfo(subject:str, To_send:str, CC_send:str, text:str):
   #メールデータを作成
   msg = MIMEMultipart()
   msg['Subject'] = subject #件名
   msg['To'] = To_send #宛先
   msg['From'] = account #差出人
   msg['Cc'] = CC_send #CC

   #テキストを追加
   bodytext = text #メモ帳の中身を記載
   txt = MIMEText(bodytext)
   msg.attach(txt)

   #Excelを添付する場合に使用
   # path_attach  = path_maillist
   # attachment = MIMEBase('application', 'vnd.ms-excel')
   # with open(path_attach, 'rb') as fp:
   #    attachment.set_payload(fp.read())

   # encoders.encode_base64(attachment)
   # attachment.add_header("Content-Disposition",
   # "attachment", filename=os.path.basename(path_attach))
   # msg.attach(attachment)
   return msg

def send_outlook_mail(msg):
   server = smtplib.SMTP('smtp.office365.com', 587)
   server.ehlo()
   server.starttls()
   server.ehlo()
   server.login(
       account,
       password
   )
   server.send_message(msg)

#メールを送付
sendpass = input() #誤送信防止:手動でパスワードを入力※出力:str
print('メール送信用のパスワードを入力してください。')

if sendpass=='123':
   for idx, data in df.iterrows():
      _subject = data['件名']
      name_To = data['担当者名']
      To_send = data['TO']
      CC_send = data['CC']
      #CCが空の時は型式がfloatになるため空文字を追加
      if pd.isna(CC_send):
         CC_send = ''
      finished = data['対応']
      
      subject = '【依頼】' + '納期変更_' + _subject + '_Xプロジェクト' #件名の作成
      maintext = re.sub('担当者', name_To, text) #宛名を置換

      #対応がNot Doneの場合は処理
      if finished == 'Not Done':
         df.loc[idx]['対応'] = 'Done' #Not Done->Doneに変更※元ファイルは変更無し
         
         msg = messageinfo(subject, To_send, CC_send, text=maintext) #メール送信情報作成
         send_outlook_mail(msg) #メール送付
      
display(df)

参考資料1:コードに使用

参考資料2:改善用

あとがき

 さらに下記ができると使いやすいけど、とりあえず自分だけで使うしさっとと使いたかったのでパパっと作成しました。
 誰か改善してくれると嬉しい

【理想の追加機能】
●pywin32とかでメーリングリストのTO、CCの部分をサクッと作成できないかな?
●対応のDone/Not Doneはできれば元ファイルを上書きしたい。Pandasだと体裁が崩れるのでOpenpyxlで自動化したい。
●件名を手動で変えられるようにtkinterで処理


 

この記事が気に入ったらサポートをしてみませんか?