【Python】csvファイルの一覧を読み込んで(いっぱい)メール送信したい


## なにこれ？
テストメールを送る機会が多いのですが、メールクライアントで1通ずつ送るのは面倒なのでcsv書けば一気に送れるツールを作りました。
docstringの書き方は良く分かってませんごめんなさい。

## 仕様
- csvファイルに送るメール情報を記載する。
- iniファイルにsmtpサーバの設定を記載する。
- csvファイルはコマンドライン引数でパスを指定する。(指定しなかったらデフォルトのcsvを使う仕様)


## 動作環境
- Python 3.9.2
- 標準モジュールしか使っていません。

## フォルダ構成
以下のファイル達が必要です。

```
- sendmail.py #プログラム本体
- sendmail.ini #設定ファイル
- testcase
 -> mailtestcase.csv #送りたいメール一覧
- attach_file
 -> attach_file.txt #添付ファイルを付けたい場合に置いてください、何でも良いです
```

## CSVファイルの内容
To,Cc,Bccを複数指定する場合は「;」区切りになります。
添付ファイルは付ける必要なければ何も入力しなくて大丈夫です。

```
from_addr,to_addrs,cc_addrs,bcc_addrs,subject,body,attach_file_name
FROMアドレス,TOアドレス,CCアドレス,BCCアドレス,件名,本文,添付ファイル名
```

## プログラム本体
```Python:sendmail.py
# -*- coding: utf-8 -*-
"""sendmail.py
    csvファイルの一覧を読み込んでメール送信するプログラム
"""

import os
import csv
import time
import sys
import configparser
from smtplib import SMTP
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.utils import formatdate

# import ini file
config=configparser.ConfigParser()
config.read('./sendmail.ini')

# params
SMTP_SERVER=config['BASE']['smtp_server']
SMTP_PORT=config['BASE']['smtp_port']
default_test_case_path=config['BASE']['default_test_case_path']
attach_file_dir=config['BASE']['attach_file_dir']

# set test case
if len(sys.argv)==2:
    if os.path.isfile(sys.argv[1]):
        test_case_path=sys.argv[1]
    else :
        print("Error:testcase does not exists.")
        sys.exit(1)
else:
    test_case_path=default_test_case_path
    print(f"Warning:testcase does not setted. use default. -> {test_case_path}")


print(f"Info:use file -> {test_case_path}")


def get_mails(test_case_path):
    """get_mails()

        csvファイルを読み込み、メールの情報を格納したdictのlistを返す関数

        Args:
            test_case_path (str): 送信するメールの一覧を記載したcsvファイルのパス

        Returns:
            list: dictを含んだlistを返す

        Raises:
            なし

        Yields:
            なし

        Examples:

            変数に格納して利用

            >>> test_mails = get_mails(test_case_path)
                test_mails = [{
                    'from_addr':'sample1@sample.com',
                    'to_addrs':['sample2@sample.com','sample3@sample.com'],
                    'cc_addrs':[],
                    'bcc_addrs':[],
                    'subject':'Test mail',
                    'body':'Test.',
                    'attach_filepath':'attach_file.txt'
                }]

        Note:
            なし

    """

    mails=[]
    header=""

    with open(test_case_path, mode='r', encoding='utf-8') as f:
        test_case_file=csv.reader(f)
        header=next(test_case_file)

        for row in test_case_file:
            mail={}
            
            mail["from_addr"]=row[0] if row[1] else ""
            mail["to_addrs"]=row[1].split(';') if row[1] else []
            mail["cc_addrs"]=row[2].split(';') if row[2] else []
            mail["bcc_addrs"]=row[3].split(';') if row[3] else []
            mail["subject"]=row[4] if row[4] else "Test"
            mail["body"]=row[5] if row[5] else "Test"
            mail["attach_filename"]=row[6]

            mails.append(mail)
    
    return mails



def create_message(mail):
    """create_message()

    メールの宛先や件名を含んだdictを受け取り、MIMEオブジェクトを返す関数

    Args:
        mail (dict): 送信するメールの情報を記載したdict
        >> mail={
            "from_addr":str,
            "to_addrs":list,
            "cc_addrs":list,
            "bcc_addrs":list,
            "subject":str,
            "body":str,
            "attach_filename":str
        }

    Returns:
        msg(MIMEオブジェクト) : MIMEオブジェクト

    Raises:
        なし

    Yields:
        なし

    Examples:
        SMTPインスタンスのsend_messageメソッドの引数に利用
        mail_server.send_message(msg=create_message(mail))

    Note:
        なし

    """

    # create mail
    msg  = MIMEMultipart()
    msg['Subject'] = mail["subject"]
    msg['From'] = mail["from_addr"]
    msg['To'] = ','.join(mail["to_addrs"])

    if mail["cc_addrs"]:
        msg['Cc']=','.join(mail["cc_addrs"])

    if mail["bcc_addrs"]:
        msg['Bcc']=','.join(mail["bcc_addrs"])

    msg['Date'] = formatdate()
    msg.attach(MIMEText(mail["body"]))

    # attach file
    if mail["attach_filename"]:
        attach_filepath=os.path.join(attach_file_dir,mail["attach_filename"])
        with open(attach_filepath, "rb") as f:
            part = MIMEApplication(f.read(),Name=mail["attach_filename"])
    
        part['Content-Disposition'] = f'attachment; filename={mail["attach_filename"]}'
        msg.attach(part)

    return msg


def send_mail(smtp_server,smtp_port,msg):
    mail_server=SMTP(smtp_server,smtp_port)
    # mail_server.debuglevel(True)
    mail_server.send_message(msg=msg)
    mail_server.close()


def main():
    test_mails=get_mails(test_case_path)
    for mail in test_mails:
        send_mail(SMTP_SERVER,SMTP_PORT,create_message(mail))
        time.sleep(1)
        print(f"Info:send mail -> {[val for val in mail.values()]}")


if __name__ == "__main__":
    main()
```

```sendmail.ini
[BASE]
smtp_server = xxx.xxx.xxx.xxx
smtp_port = 25
default_test_case_path = ./testcase/mailtestcase.csv
attach_file_dir = ./attach_file
```

## おわりに
コードを書く仕事はしていませんが、これぐらいは作れるPythonって素敵。