PR

【Python開発記③】完結編:Outlookを自動操作!実用ツールへの仕上げと販売への挑戦

スポンサーリンク
Python
スポンサーリンク

はじめに

こんにちは、ニンジンです。

これまで全3回にわたってお届けしてきた「メール自動化開発記」ですが、いよいよ今回から実用フェーズに入ります。

前回までは、ファイルを選択して記憶させる仕組みを作り、操作のベースとなる部分を整えてきました。いわば「使いやすさの土台づくり」です。

そして今回は、その土台の上に中核機能を実装していきます。

具体的には、

  • メール本文を自動生成する「差し込み処理」.format()
  • Outlookを操作してメール送信まで行う「自動化処理」pywin32

この2つ――いわばシステムの“心臓部”を組み込んでいきます。

スポンサーリンク

ステップ④:format():本文へのデータ差し込み処理の実装

ここでは、GUIで入力したテンプレートに対して、Excelのデータを差し込む処理を作っていきます。

例えば、GUIで「{氏名} 様」という雛形を用意しておき、そこにExcelにある「山田 太郎」といったデータを当てはめるイメージです。

このような「文字の差し込み」をシンプルに実現できるのが、Pythonの文字列操作メソッド .format() です。

テンプレートとデータを組み合わせることで、メール本文を自動生成できるようになります。

氏名、アドレス、会社名のデータは
第1回と同じExcelファイルを使用します。

サンプルコード mail-soft-test04.pyはこちらをクリック
import pandas as pd
import tkinter as tk
from tkinter import filedialog, scrolledtext, messagebox
import os
import json

CONFIG_FILE = "config.json"

# --- 設定ファイルの読み書き(前回パスの取得用) ---
def load_config():
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
return json.load(f)
return {"last_path": "", "last_file": "未選択"}

def save_config(file_path):
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump({"last_path": os.path.dirname(file_path), "last_file": file_path}, f, ensure_ascii=False, indent=4)

# --- メインGUIクラス ---
class MailerApp:
def __init__(self, root):
self.root = root
self.root.title("展示会メール作成ツール Pro")
self.root.geometry("700x600")

# 設定読み込み
config = load_config()
self.current_file_path = config.get("last_file", "未選択")

# 1. ファイル選択エリア
file_frame = tk.LabelFrame(self.root, text=" 1. 名簿ファイルの指定 ", padx=10, pady=10)
file_frame.pack(padx=20, pady=10, fill="x")

self.lbl_path = tk.Label(file_frame, text=f"現在のファイル: {self.current_file_path}", fg="blue", wraplength=500, justify="left")
self.lbl_path.pack(side="left", padx=5)

btn_select = tk.Button(file_frame, text="ファイル変更", command=self.select_file)
btn_select.pack(side="right")

# 2. 本文入力エリア
body_frame = tk.LabelFrame(self.root, text=" 2. 本文雛形の作成 ", padx=10, pady=10)
body_frame.pack(padx=20, pady=10, fill="both", expand=True)

tk.Label(body_frame, text="{氏名} {会社名} が自動で置換されます").pack(anchor="w")
self.txt_body = scrolledtext.ScrolledText(body_frame, font=("MS Gothic", 10), height=15, undo=True)
self.txt_body.pack(fill="both", expand=True, pady=5)

default_text = "{会社名}\n{氏名} 様\n\n昨日はありがとうございました。"
self.txt_body.insert(tk.END, default_text)

# 3. 操作ボタンエリア
btn_frame = tk.Frame(self.root)
btn_frame.pack(pady=20)

self.btn_exec = tk.Button(btn_frame, text=" 一括作成(テスト出力) ", command=self.execute, bg="#0078d4", fg="white", width=25, height=2)
self.btn_exec.pack(side="left", padx=10)

self.btn_quit = tk.Button(btn_frame, text=" 終了 ", command=self.root.destroy, width=10, height=2)
self.btn_quit.pack(side="left", padx=10)

def select_file(self):
config = load_config()
initial_dir = config.get("last_path", os.path.expanduser("~"))

file_path = filedialog.askopenfilename(title="Excelを選択", initialdir=initial_dir, filetypes=[("Excel", "*.xlsx")])

if file_path:
self.current_file_path = file_path
self.lbl_path.config(text=f"現在のファイル: {file_path}")
save_config(file_path)

def execute(self):
if not os.path.exists(self.current_file_path):
messagebox.showerror("エラー", "Excelファイルが選択されていないか、存在しません。")
return

template = self.txt_body.get("1.0", tk.END).strip()

try:
df = pd.read_excel(self.current_file_path)
print("\n" + "="*50 + "\n 送信プレビュー \n" + "="*50)
for _, row in df.iterrows():
print(f"TO: {row['アドレス']}")
print(template.format(**row.to_dict()))
print("-" * 30)
messagebox.showinfo("完了", f"{len(df)}件のプレビューをターミナルに出力しました。")
except Exception as e:
messagebox.showerror("エラー", f"処理に失敗しました:\n{e}")

if __name__ == "__main__":
root = tk.Tk()
app = MailerApp(root)
root.mainloop()

※VSCodeなどのエディターにてサンプルコードを実行して下さい

  • コードを実行すると、画像のGUIが立ち上がります。
  • 右上の「ファイル変更」をクリック
  • Windowが立ち上がります。
  • 対象のExcelファイルを選択し、「開く」をクリック
  • ファイルが選択されGUIへ戻ります。
  • 一括作成(テスト出力)を押下すると、本文内に記載されている下記の表示箇所にExcelデータが差し込まれます。
    {会社名}
    {氏名}

出力に成功すると「完了」のダイアログが表示されます

VSCodeのターミナルにて出力されれば成功です!

ここが工夫:row.to_dict() の活用

差し込み処理を作っていると、こんな問題が出てきます。
「項目が増えるたびに、プログラムを書き直さないといけない…」
たとえば「役職」や「部署名」といった列をExcelに追加するたびに、コードを修正するのは手間ですよね。

そこで役立つのが、row.to_dict() という書き方です。

これを使うと、Excelの1行分のデータを「項目名=値」のセットとしてまとめて扱うことができます。
その結果、テンプレート側に
{役職}{部署名} と書くだけで、自動的に対応するデータが差し込まれるようになります。

つまり――
Excelの列を増やしても、プログラムはそのままでOK。

将来の拡張にも強い、メンテナンス性の高い設計になります。

スポンサーリンク

■ ステップ⑤:pywin32:Outlookを操作してメールを送る

ここでは、いよいよメール送信の自動化を実装していきます。

使用するのは pywin32
これを使うことで、PythonからOutlookを直接操作できるようになります。

今回のポイントは、これまで作ってきた execute メソッドの中身を、実際のOutlook操作に置き換えることです。

つまり――
今までの「処理の流れ」はそのままに、中身だけを“本物のメール送信”に差し替えるイメージです。

実際にやってみると分かるのですが、
Outlookの操作は驚くほどシンプルです。

「もっと複雑だと思っていたのに…」と感じるくらい、あっけなく動いてしまいます。

この手軽さこそが、pywin32を使う大きなメリットのひとつです。

サンプルコード mail-soft-test05.pyはこちらをクリック
import pandas as pd
import tkinter as tk
from tkinter import filedialog, scrolledtext, messagebox
import os
import json
import win32com.client # Outlook操作用
import time

CONFIG_FILE = "config.json"

def load_config():
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
return json.load(f)
return {"last_path": "", "last_file": "未選択"}

def save_config(file_path):
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump({"last_path": os.path.dirname(file_path), "last_file": file_path}, f, ensure_ascii=False, indent=4)

class MailerApp:
def __init__(self, root):
self.root = root
self.root.title("展示会メール作成ツール Pro")
self.root.geometry("700x600")

config = load_config()
self.current_file_path = config.get("last_file", "未選択")

# 1. ファイル選択
file_frame = tk.LabelFrame(self.root, text=" 1. 名簿ファイルの指定 ", padx=10, pady=10)
file_frame.pack(padx=20, pady=10, fill="x")
self.lbl_path = tk.Label(file_frame, text=f"現在のファイル: {self.current_file_path}", fg="blue", wraplength=500, justify="left")
self.lbl_path.pack(side="left", padx=5)
btn_select = tk.Button(file_frame, text="ファイル変更", command=self.select_file)
btn_select.pack(side="right")

# 2. 件名入力エリア
subject_frame = tk.LabelFrame(self.root, text=" 2. 件名の入力 ", padx=10, pady=10)
subject_frame.pack(padx=20, pady=10, fill="x")

tk.Label(subject_frame, text="件名を入力してください").pack(anchor="w")
self.txt_subject = tk.Entry(subject_frame, font=("MS Gothic", 10), width=50)
self.txt_subject.pack(side="left", padx=5)

# 3. 本文入力エリア
body_frame = tk.LabelFrame(self.root, text=" 3. 本文雛形の作成 ", padx=10, pady=10)
body_frame.pack(padx=20, pady=10, fill="both", expand=True)
tk.Label(body_frame, text="{氏名} {会社名} が自動で置換されます").pack(anchor="w")
self.txt_body = scrolledtext.ScrolledText(body_frame, font=("MS Gothic", 10), height=15, undo=True)
self.txt_body.pack(fill="both", expand=True, pady=5)

default_text = "{会社名}\n{氏名} 様\n\n昨日はありがとうございました。"
self.txt_body.insert(tk.END, default_text)

# 4. 操作ボタン
btn_frame = tk.Frame(self.root)
btn_frame.pack(pady=20)
# 初期状態は青
self.btn_exec = tk.Button(btn_frame, text=" 一括作成(実行) ", command=self.execute, bg="#0078d4", fg="white", width=25, height=2)
self.btn_exec.pack(side="left", padx=10)
self.btn_quit = tk.Button(btn_frame, text=" 終了 ", command=self.root.destroy, width=10, height=2)
self.btn_quit.pack(side="left", padx=10)

def select_file(self):
config = load_config()
initial_dir = config.get("last_path", os.path.expanduser("~"))
file_path = filedialog.askopenfilename(title="Excelを選択", initialdir=initial_dir, filetypes=[("Excel", "*.xlsx")])
if file_path:
self.current_file_path = file_path
self.lbl_path.config(text=f"現在のファイル: {file_path}")
save_config(file_path)
# ファイルを変えたらボタンの色を戻す
self.reset_button()

def reset_button(self):
self.btn_exec.config(text=" 一括作成(実行) ", state="normal", bg="#0078d4", fg="white")

def execute(self):
if not os.path.exists(self.current_file_path):
messagebox.showerror("エラー", "Excelファイルが選択されていません。")
return

# ボタンを「送信中」状態にする(黄色・無効化)
self.btn_exec.config(text="送信中...", state="disabled", bg="#ffc107", fg="black")
self.root.update()

template = self.txt_body.get("1.0", tk.END).strip()

try:
# Outlookの起動
outlook = win32com.client.Dispatch("Outlook.Application")
df = pd.read_excel(self.current_file_path)

count = 0
for _, row in df.iterrows():
# メール作成
mail = outlook.CreateItem(0)
mail.to = row['アドレス']
mail.Subject = self.txt_subject.get().strip() or "(件名なし)"

# 差し込み処理
mail.Body = template.format(**row.to_dict())

# 送信(テスト時は .Display() に変えると画面が立ち上がるだけで止まる)
mail.Send()
count += 1
time.sleep(0.5) # 連続送信による負荷軽減

# 完了状態にする(緑色)
self.btn_exec.config(text=f"送信完了({count}件)", state="normal", bg="#28a745", fg="white")
messagebox.showinfo("完了", f"全 {count} 件の送信を完了しました。\nOutlookの送信済みアイテムを確認してください。")

except Exception as e:
self.reset_button()
messagebox.showerror("エラー", f"処理に失敗しました:\n{e}")

if __name__ == "__main__":
root = tk.Tk()
app = MailerApp(root)
root.mainloop()

※VSCodeなどのエディターにてサンプルコードを実行して下さい

  • コードを実行すると、画像のGUIが立ち上がります。
    ※件名入力が追加されています。
  • 右上の「ファイル変更」をクリック。
  • Windowが立ち上がります。
  • 対象のExcelファイルを選択して、「開く」をクリック。
  • 選択されたExcelファイルが表示されます
  • 件名に文字を入力します。
  • 一括作成(実行)ボタンをクリックし送信します。
  • メール送信に成功すると「完了」のダイアログが表示されます。
  • Outlookを開いて、送信済みアイテムを開くと送信記録が残っています。

実装のポイント解説

今回の処理の中で、押さえておきたいポイントをいくつか紹介します。

▼Outlook操作の準備
import win32com.client

これが、Outlookを操作するためのライブラリです。
いわば「PythonからOutlookを動かすための入り口」になります。

▼実行中の状態を分かりやすくする
self.btn_exec.config(...)

execute が呼ばれたタイミングで、ボタンの表示を変更しています。

  • 実行中 → 黄色(送信中)
  • 完了後 → 緑(完了)

処理の進行状況が一目で分かるようになるので、ユーザーにとっても安心感があります。

▼実際のメール送信
mail.Send()

ここが実際にメールを送信する本番処理です。
ただし、いきなり送信するのが不安な場合は、テスト方法も用意できます。

mail.Display()

こちらに書き換えると、メールは送信されず、Outlookの新規メール画面が開くだけになります。
差し込み内容の確認など、テスト段階ではこちらを使うのがおすすめです。


ここまでで出来ること(ブログ版の到達点

第1回〜第3回までで紹介してきたPythonコードを組み合わせることで、

  • Excelファイルからデータを読み込む
  • GUIでメール本文を作成する
  • データを差し込んで本文を自動生成する
  • Outlookを操作してメールを送信する

という、一連の基本的な自動化フローが実現できるようになりました。

いわば「メール自動送信ツールの基礎」は、この時点で完成しています。

ただし、このままでは“製品”にはならない

ここまででも十分便利ですが、
「ワンコインとはいえ販売する」となると、この機能のままでは不十分です。

実際の業務で使うには、もう一歩踏み込んだ“使いやすさ”と“安全性”が必要になります。

そこで NINJIN Mail では、以下のような機能を追加・強化しています。

スポンサーリンク

■ 販売版との主な違い

① 機能面のアップグレード

  • UIのモダン化:
    tkinter から customtkinter に刷新し、ダークモードにも対応。より直感的で使いやすいデザインに改善しています。
  • テンプレート保存(最大3パターン)
    タブで切り替えながら複数の本文を管理可能。終了時も自動保存されます。
  • 署名管理機能:
    本文とは別に署名を管理でき、すべてのメールに自動で付与されます。
  • 共通添付ファイル機能:
    複数ファイルをまとめて添付し、全宛先に一括送信できます。
  • 送信前プレビュー:
    「最初の1通のみ確認」機能により、実際の差し込み結果を事前チェック可能です。
  • 送信ログの記録(目玉機能):
    Excelに「送信日」「結果(成功/エラー)」「使用テンプレート」などを自動で書き戻します。

②エラー対策・安全性の強化

  • メールアドレスの形式チェック:
    不正なアドレスは送信前に検知して警告します。
  • 空欄データへの対応:
    氏名未入力の警告や、空欄項目を安全に処理する仕組みを追加。
  • 二重送信の防止:
    ファイルが開かれている場合でも「RECOVERY」として保存し、データ消失を防ぎます。
  • 送信間隔の調整:
    time.sleep(0.5) により負荷を分散し、フリーズやエラーを防止。
  • 設定ファイルの耐障害性向上:
    config.json の不足項目を自動補完し、エラーを防ぎます。
スポンサーリンク

■ 最後に:エンジニアの挑戦

大手企業なら専用システムがありますが、中小企業の営業現場では、今も手作業で1件ずつコピペしている方がたくさんいます。 「そんな方々の苦労を、自分の技術で少しでも減らしたい」。そんな想いでこのツールを作りました。

実験結果(何個売れたのか?)は、また期間を置いて発表しますね。正直、売れないとは思っていますが……(笑)

もし、「自分で作るのは大変だけど、今すぐこの機能が欲しい!」という方や、活動を応援してくださる方がいれば、Noteにてフルコード付きで販売しております。ぜひチェックしてみてください!

実際にNoteにて販売中!
Codeに興味がある方
Codeとか面倒くさいと思う方
是非チェックしてみて下さい

実験販売ですが、期待もしつつ、まぁ売れないよなと思っていますw
応援購入お待ちしております。

スポンサーリンク

NINJIN Mailシリーズはこちら

【Python開発記①】展示会お礼メールを自動化したい!Excel読み取り編

【Python開発記②】「たかが1クリック」を削る!ファイル選択と設定保存の実装

コメント