2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Python】JSONファイルのデータを元にOutlook新規メールを作成するツールを作成したことと、引っ掛かった部分について

Last updated at Posted at 2020-01-01

#1.この記事について
主目的は、
日次メール作成作業や月次定期メールなど
定型的な作業を効率化したかったこと。

第二の目的は、
Pythonにおけるjsonファイルの扱いや
Outlookの連携を学習したかった。
(率直なところOutlookをいじるだけなら
Excelにデータを載せてVBAで作成するのが
一番正着なんではないかと思うけど)

######※ツールとソースコードはGithubに上げてます(後述)

#2.やりたいこと
・jsonファイルの項目を考える
・jsonファイルをハンドリングするクラス作成
・Office Outlookをハンドリングするクラス作成
・上記クラスを連携してメール作成するmainメソッドの開発

#3.使用したツール・環境
・Windows10
・Python 3.7.0
・office2016
・その他下記コードに記載のライブラリ各種

#4.作成したコードと解説

##クラス図
pycharmで作成したクラス図はこのようになる。
OutlookMailCreater.png

##jsonファイルの中身

要件としては、
・フォーマット(テキストメールかHTMLメールかリッチテキストか)
・差出人
・送り先(複数指定可)
・CC(複数指定可)
・BCC(複数指定可)
・件名
・本文
・添付ファイル
を設定できるものが望ましかったため、
このようになった。

mailSetting.json
{
  "mailFormat": 3,
  "senderAddress": "foo@gmail.com",
  "receiverAddressSet": ["bar@Test.jp","bar2@Test.jp","bar3@Test.jp"],
  "CC_AddressSet": ["bar@Test.jp","bar4@Test.jp"],
  "BCC_AddressSet": ["bar@Test.jp","bar5@Test.jp"],
  "title":"あいうえお",
  "attachedFileFolderPath": "C:\\Users\\UserName\\Test",
  "body": "〇〇様\n\nお疲れさまです。〇〇です。\n\n〇〇を送付します。\n\n以上。\n"
}

mailFormatは1~3の数字から選び、
それぞれが[テキストメールかHTMLメールかリッチテキストか]に該当しているようだ。

##jsonハンドリングクラス

JsonHandler.py
import json


class JsonHandler:
    def __init__(self, jsonPath):
        # jsonファイル読み込み
        self.file = open(jsonPath, 'r', encoding="utf-8_sig")
        self.jsonData = json.load(self.file)

    # 一次ネストのjsonデータ取得
    def getParam_OneNest(self, args):
        return self.jsonData[args]

    def __del__(self):
        # ファイル閉じる
        self.file.close()

###引っ掛かった点①:エンコーディングについて

ファイル取込の際のエンコードの方法を検索しましたが、
こちらの記事を参考にさせていただいた。
WindowsでCP932(Shift-JIS)エンコード以外のファイルを開くのに苦労した話 - Qiita

どうやらBOMの処理のためにencoding="utf-8_sig"という方法を採用する必要があるようだ。

##outlookハンドリングクラス

OutlookHandler.py

import glob
import os

import win32com.client


class OutlookHandler:
    def __init__(self):
        self.outlook = win32com.client.Dispatch("Outlook.Application")
        self.newMail = None

    def create(self):
        self.newMail = self.outlook.CreateItem(0)

    def setting(self, argMailformat, argSenderaddress,
                argReceiveraddressset, argCc_Addressset,
                argBcc_Addressset, argTitle,
                argAttachedfilefolderpath, argBody):

        # ///メールアイテムに情報設定
        self.newMail.BodyFormat = argMailformat
        try:
            self.newMail._oleobj_.Invoke(*(64209, 0, 8, 0, self.outlook.Session.Accounts(argSenderaddress)))
        except:
            pass
        self.newMail.To = argReceiveraddressset
        self.newMail.cc = argCc_Addressset
        self.newMail.Bcc = argBcc_Addressset
        self.newMail.Subject = argTitle
        # 本文に署名を付与(Outlook起動時のみ)
        try:
            self.newMail.Body = argBody \
                                + os.linesep \
                                + self.newMail.GetInspector.WordEditor.Bookmarks('_MailAutoSig').Range.Text
        except:
            self.newMail.Body = argBody

        # 添付ファイル取得
        if os.path.isdir(argAttachedfilefolderpath):
            for file in glob.glob(argAttachedfilefolderpath + '\\*'):
                if os.path.isfile(file):
                    self.newMail.Attachments.Add(file)

    def display(self):
        self.newMail.Display(False)

    def release(self):
        self.newMail = None
        self.outlook = None


###引っ掛かった点②:差出人メールアドレス設定について
こんなへんてこなやり方をしている。

self.newMail._oleobj_.Invoke(*(64209, 0, 8, 0, self.outlook.Session.Accounts(argSenderaddress)))

VBAの場合に定石だった
「SentOnBehalfOfName」フィールドがなぜか使えなかった(outlook側の設定の問題かもしれないけど)。
調べたらこんな記事がヒットしたため、それを採用。
https://stackoverflow.com/questions/35908212/python-win32com-outlook-2013-sendusingaccount-return-exception

ただしこの方法は、jsonで設定したアドレスがoutlookにユーザーのアドレスとして登録されていることが前提。
もし登録されていなかったら例外が発生するのでtrycatchで処理している。
SentOnBehalfOfNameのように登録していないものでも使えるというわけではない。
そのため、改良の余地はある。
(デフォルトのアドレスしか使わない場合は特に問題もない)

###引っ掛かった点③:本文に署名を乗せる方法について

self.newMail.Body = argBody \
                            + os.linesep \
                            + self.newMail.GetInspector.WordEditor.Bookmarks('_MailAutoSig').Range.Text  # 本文に署名を付与

このようにWordEditor.Bookmarksのフィールドを使用した方法だと、
「※outlookアプリケーション起動時にしか署名が付与されない」という大きな問題がある。
もし起動していない状態でmainメソッドを実行したら例外が発生する。

そのため、今回はtrycatchで処理して、
もし起動してないなら署名は諦めることにしている。

これも改良の余地がある部分だけど、仕方ない(outlook起動処理はそれはそれでけっこうめんどくさいので)。

##mainメソッド

OutloookMailCreater.py
import datetime
import sys

# モジュール検索パスを再設定
# ダブルクリックによる起動にも対応できるようにする
sys.path.append(r"C:\Users\UserName\PycharmProjects\CreateToolAndTest")
import os
import tkinter
from tkinter import messagebox
from PrivateTool.OutlookMailCreater.JsonHandler import JsonHandler
from PrivateTool.OutlookMailCreater.OutlookHandler import OutlookHandler
import traceback

if __name__ == '__main__':

    try:

        # messageウィンドウの準備
        root = tkinter.Tk()
        root.withdraw()
        root.attributes("-topmost", True)

        # outlook起動
        outlookHandler = OutlookHandler()
        outlookHandler.create()

        # jsonファイル操作オブジェクト
        jsonHandler = JsonHandler(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'mailSetting.json'))

        # jsonから値を取得しリストに格納
        settingList = [jsonHandler.getParam_OneNest('mailFormat'), ]
        settingList.append(jsonHandler.getParam_OneNest('senderAddress'))
        tempSet = jsonHandler.getParam_OneNest('receiverAddressSet')
        settingList.append('; '.join(tempSet))
        tempSet = jsonHandler.getParam_OneNest('CC_AddressSet')
        settingList.append('; '.join(tempSet))
        tempSet = jsonHandler.getParam_OneNest('BCC_AddressSet')
        settingList.append('; '.join(tempSet))
        settingList.append(jsonHandler.getParam_OneNest('title'))
        settingList.append(jsonHandler.getParam_OneNest('attachedFileFolderPath'))
        settingList.append(jsonHandler.getParam_OneNest('body'))

        # ◆◆◆リストデータになにか処理を加える場合はここに記述◆◆◆
        # myDate = datetime.date.today()
        # if myDate.day < 16:
        #     myDate -= datetime.timedelta(days=15)
        # settingList[5] = settingList[5].replace('$date', myDate.strftime('%Y%m'))
        # ◆◆◆◆◆◆

        # outlookの新規メールアイテム作成
        outlookHandler.setting(*settingList)

        # メールアイテムの表示
        outlookHandler.display()
        outlookHandler.release()

        # アナウンス
        messagebox.showinfo("", "正常終了")

    except:
        print("Error", messagebox.showinfo("Error", "エラー発生"))
        traceback.print_exc()
    finally:
        pass


###引っ掛かった点④:messageウィンドウを最前面に出す方法について
次のように設定してあげればいいらしい。

root.attributes("-topmost", True)

#5.終わりに

ツールとソースコードはこちら↓
CreateToolAndTest/Tool_Python/OutlookMailCreater at master · dede-20191130/CreateToolAndTest

なにか補足がありましたらコメントください。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?