本記事では、チュートリアルとしてFlaskによるcontact-form作成から以下の学習を体験できます。
- PRGパターン(POST/REDIRECT/GET)の理解
 - request, redirectモジュールの理解と使い方
 - formの使い方
 
上記を中心にFlaskチュートリアルを進めていきます。
requestの使い方
ここでは、requestの使い方としてパラメータの取得方法を解説します。
以下は、代表的なrequestオブジェクトの属性とメソッドになります。
| 属性またはメソッド | 説明 | 
|---|---|
| method | リクエストメソッド | 
| form | リクエストフォーム | 
| args | クエリパラメータ | 
| cookies | リクエストクッキー(Cookie) | 
| files | リクエストファイル | 
| environ | 環境変数 | 
| headers | リクエストヘッダー | 
| referrer | リクエストのリファラ(リンク元のページ) | 
| query_string | リクエストクエリ文字列 | 
| Scheme | リクエストのプロトコル(http/https) | 
| url | リクエストURL | 
また、GETリクエストとPOSTリクエストによってデータの取得方法は異なるため注意が必要です。
パラメータの取得方法
以下は、一般的なパラメータの取得方法の種類です。
- GET/POSTリクエストパラメータ
 - URI(パス)パラメータ
 
GET/POSTリクエストパラメータ
以下は、GETリクエストのパラメータの取得方法になります。
| コード | 説明 | 
|---|---|
| request.args[key] | GETパラメータ(Keyがなければエラー) | 
| request.args.get(key) | GETパラメータ(KeyがなければNone) | 
| request.args.get(key, “…”) | GETパラメータがない場合のデフォルト値を指定 | 
| request.args.get(key, type=int) | GETパラメータをint型で受け取る | 
以下は、POSTリクエストのパラメータの取得方法になります。
| コード | 説明 | 
|---|---|
| request.form[key] | POSTパラメータ(Keyがなければエラー) | 
| request.form.get(key) | POSTパラメータ(KeyがなければNone) | 
| request.form.get(key, “…”) | POSTパラメータがない場合のデフォルト値を指定 | 
| request.form.get(key, type=int) | POSTパラメータをint型で受け取る | 
URI(パス)パラメータ
以下は、URI(パス)パラメータを使用する際のサンプルコードになります。
@app.route("/user/<user_id>")
def user(user_id):
  print(user_id)URIパラメータの場合はデコレータ関数である@app.route()にてパスの一部として扱います。
また、関数であるuser()の引数に指定することで値を受け取れます。
redirectの使い方
ここでは、redirectの使い方を解説します。
- redirectによるURLの指定
 - redirectによる関数名の指定
 
それぞれのパターンを確認しておきましょう。
redirectによるURLの指定
redirectを利用することで、指定したURLに遷移できます。
from flask import redirect
@app.route("/")
def main():
    return redirect("https://jobcode.jp/")redirectによる関数名の指定
redirect()にurl_forを利用することで関数名の指定もできます。
from flask import redirect, url_for
@app.route("/")
def main():
    ...省略...
@app.route("/sub_main")
def sub_main():
    return redirect(url_for("main"))問い合わせフォームの仕様
本チュートリアルは、以下のシンプルなcontact-formを作成します。


- 問い合わせフォーム画面
 - 問い合わせ完了画面
 
上記の2画面にて構成します。
ここでは、特にデータベースを利用せず問い合わせフォームから問い合わせすると、入力したメールアドレスに内容を送信し問い合わせ完了になります。
PRGパターンとは
PRGパターンはPOST/REDIRECT/GETの略です。
フォームデータを送信(POST)したら再読み込み(REDIRECT)し、取得(GET)したページを表示することを指します。
PRGパターンを使わない場合、フォームデータをPOSTしたあと再ロードすると元のコンテンツが再送され、フォームデータが二重で送られる可能性があります。
上記の問題を回避するために多くのケースでPRGパターンを利用します。
本チュートリアルでは、以下の工程を考えます。
- contact-form画面を表示する(GET)
 - 問い合わせ内容をメールで送信する(POST)
 - 問い合わせ完了画面へリダイレクトする(REDIRECT)
 - 問い合わせ完了画面を表示する(GET)
 
また、ルート情報は以下の表になります。
| Endpoint | Methods | Rule | 
|---|---|---|
| contact | GET | /contact | 
| contact_complete | GET, POST | /contact/complete | 
request(リクエスト)とredirect(リダイレクト)
リクエスト情報を取得するには、flaskモジュールからrequestをimportします。
また、別のエンドポイントをリダイレクトするにはredirect関数を利用します。
# 必要なモジュールをインポート
from flask import Flask, render_template, url_for, request, redirectcontact-formのエンドポイントを作成する
任意で作成したディレクトリにてapp.pyを作成し、以下のエンドポイントを追加します。
- contact-form画面を表示するエンドポイント
 - リダイレクト後の問い合わせ完了画面を表示するエンドポイント
 
メール送信処理はコメントにて一旦保留にして後述します。
# Flaskのインポート
from flask import Flask, render_template, url_for, request, redirect
# Flaskクラスをインスタンス化
app = Flask(__name__)
# URLと実行する関数をマッピング
@app.route("/contact", methods=["GET"])
def contact():
    return render_template("contact.html")
@app.route("/contact/complete", methods=["GET", "POST"])
def contact_complete():
    if request.method == "POST":
        # メール送信は一旦保留(後述)
        # contact_completeエンドポイントへリダイレクト
        return redirect(url_for("contact_complete"))
    
    return render_template("contact_complete.html")- contact-form画面を返すcontactエンドポイント作成
 - contact-form処理/完了画面を返すcontact_completeエンドポイント作成
@app.route()デコレータの第二引数にmethods=[“GET”, “POST”]を指定し、GETとPOSTメソッドを許可 - request.method属性を利用してリクエストされたメソッドをチェック
 - GETの場合は問い合わせ完了画面を返す
POSTの場合はcontact_completeエンドポイントへリダイレクト 
contact-formのテンプレートを作成する
任意のディレクトリ配下にtemplatesディレクトリを作成し、問い合わせフォーム画面であるcontact.htmlと問い合わせ完了画面であるcontact_complete.htmlを作成します。
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>CONTACT</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css' )}}" />
  </head>
  <body>
    <div class="contact-bg">
      <div class="contact-area inner">
        <p class="contact-message">
          お問い合わせ、ご要望等がございましたら、お気軽にお問い合わせください。<br>
          下記のプライバシーポリシーをご確認の上、送信してください。<br>
          <span class="message-notice"><span class="essential">必須</span>は必須項目です。</span>
        </p>
        <p class="text notosan">
          <a href="#">送信にあたっては、こちらのプライバシーポリシーをご覧ください。</a>
        </p>
          <div class="contact-inner">
            <form action="{{ url_for('contact_complete' )}}" method="POST" novalidate="novalidate">
              <table class="contact-table">
                <tr class="table-list">
                  <th>
                    <label for="name">お名前<span class="essential">必須</span></label>
                  </th>
                  <td>
                    <input type="text" name="username" value="{{ username }}" placeholder="お名前をご入力ください" class="input-area" autocomplete="name">
                  </td>
                </tr>
                <tr class="table-list">
                  <th>
                    <label for="mail">メールアドレス<span class="essential">必須</span></label>
                  </th>
                  <td>
                    <input type="text" name="mail" value="{{ email }}" placeholder="メールアドレスをご入力ください" class="input-area" autocomplete="email">
                  </td>
                </tr>
                <tr class="table-list">
                  <th>
                    <label for="description">お問い合わせ内容<span class="essential">必須</span></label>
                  </th>
                  <td>
                    <textarea id="description" name="description">{{ description }}</textarea>
                  </td>
                </tr>
              </table>
            </form>
            <input type="submit" value="送信する" class="submit-button">
          </div>
      </div>
    </div>
  </body>
</html><!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>CONTACT-COMPLETE</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css' )}}" />
  </head>
  <body>
    <div class="contact-bg">
      <div><h3 class="contact-h3">CONTACT</h3></div>
      <div><h1 class="contact-h1">お問い合わせ完了</h1></div>
      <div class="contact-area inner">
        <p class="contact-message">
          お問い合わせありがとうございます。<br>
          担当者よりご連絡させていただきます。<br>
        </p>
        <p class="text notosan">
          <a href="#">TOPページに戻る</a>
        </p>
      </div>
    </div>
  </body>
</html>任意のディレクトリ配下に、static/style.cssを作成し、以下のスタイルコードを追加します。
.contact-bg {
    background: #e4eeef;
    padding: 100px 0;
    margin: 0 auto 0;
}
.contact-h1 {
    text-align: center;
}
.contact-h3 {
    text-align: center;
}
.contact-message {
    text-align: center;
    font-size: 14px;
    line-height: 2;
    margin-bottom: 50px;
}
.message-notice {
    color: #c10811;
    font-size: 14px;
}
.essential {
    background: #c10811;
    color: #fafafa;
    font-size: 12px;
    padding: 0 10px;
    font-weight: normal;
    margin-left: 10px;
}
.contact-area {
    background: #fafafa;
    padding: 90px;
    margin: 100px auto;
    width: 1000px;
}
.contact-table {
    width: 100%;
}
.table-list {
    display: flex;
    justify-content: space-between;
    font-family: 'Noto Sans JP', sans-serif;
    letter-spacing: 0.05em;
    width: 100%;
    margin-bottom: 40px;
}
.table-list th {
    font-size: 13px;
    font-weight: bold;
    width: 250px;
    text-align: left;
}
.table-list-address {
    flex-wrap: wrap;
}
.table-list-address .input-area {
    margin-bottom: 10px;
}
.input-area {
    font-family: 'Noto Sans JP', sans-serif;
    letter-spacing: 0.05em;
    padding: 0 10px;
    border: none;
    width: 550px;
    height: 40px;
    box-sizing: border-box;
    border: 1px solid #c4c4c4;
}
.table-list td {
    font-size: 13px;
    width: calc(100% - 250px);
}
input::placeholder {
    color: #bfbfbf;
    font-size: 12px;
    font-weight: bold;
}
textarea {
    border: none;
    width: 550px;
    height: 200px;
    padding: 0;
    border: 1px solid #c4c4c4;
    resize: vertical; /* 横方向のみサイズを固定する */
}
textarea::placeholder {
    color: #bfbfbf;
    font-size: 12px;
}
input[type="text"] {
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}
.contact-area .text {
    font-size: 13px;
    text-align: center;
    margin-bottom: 100px;
}
.contact-area .text a {
    color: #000;
    border-bottom: 1px solid #000;
    transition: all .3s;
    text-decoration: none;
}
.contact-area .text a:hover {
    border-bottom: 1px solid #777;
    padding-bottom: 5px;
    color: #777;
}
.submit-button {
    box-sizing: border-box;
    position: relative;
    display: block;
    margin: 30px auto 0;
    background-color: #e4eeef;
    cursor: pointer;
    border: 1px solid #e4eeef;
    color: #000;
    text-align: center;
    text-decoration: none;
    line-height: 1.5;
    outline: none;
    -webkit-transition: all .3s;
    transition: all .5s;
    padding: 20px 100px;
}
.submit-button:hover {
    background: #cae1e3;
    color: #000;
    border: 1px solid #cae1e3;
}
@media(max-width:1200px) {
    .contact-area {
        width: 80%;
        padding: 60px;
    }
}
@media(max-width:1024px) {
    .contact-area {
        padding: 50px 15px;
    }
    .table-list th {
        width: 180px;
    }
    .table-list td {
        width: 100%;
    }
    .input-area {
        width: 500px;
        height: 40px;
    }
    textarea {
        width: 500px;
        height: 200px;
    }
    .contact-message {
        margin-bottom: 30px;
    }
    .contact-area .text {
        margin-bottom: 30px;
    }
}
@media(max-width:834px) {
    .contact-bg {
        margin: 50px auto 0;
        padding: 50px 0;
    }
    .contact-area {
        margin: 50px auto;
        padding: 50px 20px;
    }
    .check-box label {
        width: 100%;
    }
    .input-area {
        width: 100%;
        height: 30px;
    }
    textarea {
        width: 100%;
        height: 200px;
    }
    .table-list {
        flex-wrap: wrap;
        margin-bottom: 20px;
    }
    .table-list th {
        font-size: 12px;
        width: 200px;
        margin-bottom: 10px;
    }
    .table-list td {
        font-size: 12px;
    }
    .table-list td {
        width: 100%;
    }
    .contact-message {
        font-size: 13px;
    }
    .contact-area .text {
        font-size: 13px;
    }
}
@media (max-width:640px) {
    .contact-message {
        text-align: left;
    }
    .contact-area .text {
        text-align: left;
    }
}
@media(max-width:320px) {
    .input-area {
        width: 100%;
    }
    textarea {
        width: 100%;
    }
}上記のファイル群を追加し、任意のディレクトリにてflask runコマンドを実行すると確認できます。


POSTされたformの値を取得する
POSTされたformの値を取得するには、requestのform属性を利用します。
app.pyのcotanct_completeエンドポイントを修正します。
# Flaskのインポート
from flask import Flask, render_template, url_for, request, redirect
# Flaskクラスをインスタンス化
app = Flask(__name__)
# URLと実行する関数をマッピング
@app.route("/contact", methods=["GET"])
def contact():
    return render_template("contact.html")
@app.route("/contact/complete", methods=["GET", "POST"])
def contact_complete():
    if request.method == "POST":
        # form属性から値を取得する
        username = request.form["username"]
        email = request.form["email"]
        description = request.form["description"]
        # contact_completeエンドポイントへリダイレクト
        return redirect(url_for("contact_complete"))
    
    return render_template("contact_complete.html")Flashメッセージ(email-validator)
Flashメッセージは、処理後にメッセージを表示する機能です。
完了時やエラー時など、一時的なメッセージを表示したい場合に利用します。
Flashメッセージを利用するために以下の処理を追加します。
- SECRET_KEYを設定する
 - バリデーションを追加する
 
SECRET_KEYを設定する
contact-form画面にバリデーション(入力チェック処理)を追加します。
入力チェック時にエラーが発生した場合、Flashメッセージを表示します。
Flashメッセージはflash関数にて設定し、テンプレートにてget_flashed_messages関数で取得し表示します。
セッションが必要になるため、コンフィグのSECRET_KEYを設定します。
ここでは、コンフィグファイルではなく、app.pyに書き込んでいます。
以下のコードでコンフィグを追加・設定することができます。
app.config["config_key"] = config_valueセッション(Session)を利用するためには、セッション情報をセキュリティで守る秘密鍵(SECRET_KEY)を設定します。
秘密鍵はランダムな値にする必要があります。
ここでは、以下のサイトを利用してランダムな秘密鍵を生成しました。

# ...省略...
app = Flask(__name__)
# SECRET_KEYを追加する
app.config["SECRET_KEY"] = "生成したパスワード"バリデーションを追加する
次に、バリデーション(入力チェック処理)を追加します。
また、メールアドレスが正しいかチェックするemail-validatorパッケージをインストールします。
pip install email-validator# Flashの追加
from flask import Flask, render_template, url_for, request, redirect, flash
# email_validatorをインポート
from email_validator import validate_email, EmailNotValidError
# Flaskクラスをインスタンス化
app = Flask(__name__)
# SECRET_KEYを追加する
app.config["SECRET_KEY"] = "生成したパスワード"
# URLと実行する関数をマッピング
@app.route("/contact", methods=["GET"])
def contact():
    return render_template("contact.html")
@app.route("/contact/complete", methods=["GET", "POST"])
def contact_complete():
    if request.method == "POST":
        # form属性から値を取得する
        username = request.form["username"]
        email = request.form["email"]
        description = request.form["description"]
        
        # バリデーションチェック追加
        is_valid = True
        if not username:
            flash("ユーザー名は必須です")
            is_valid = False
        if not email:
            flash("メールアドレスは必須です")
            is_valid = False
        try:
            validate_email(email)
        except EmailNotValidError:
            flash("メールアドレス形式で入力してください")
            is_valid = False
        if not description:
            flash("問い合わせ内容は必須です")
            is_valid = False
        if not is_valid:
            return redirect(url_for("contact"))
        
        # contact_completeエンドポイントへリダイレクト
        return redirect(url_for("contact_complete"))
    
    return render_template("contact_complete.html")- Flaskにてflashを追加インポート
 - メールアドレス形式チェック用でvalidate_emailとEmailNotValidErrorをインポート
 - validate_email関数にてチェックし、形式が不正の場合に例外処理としてtry-except文で囲む
 - is_validがFalseの場合はリダイレクト
 
flashに設定したエラーメッセージをcontact-form画面で表示するために、HTMLファイルでget_flashed_messsages関数を利用します。
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>CONTACT</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css' )}}" />
  </head>
  <body>
    <div class="contact-bg">
      <div class="contact-area inner">
        <p class="contact-message">
          お問い合わせ、ご要望等がございましたら、お気軽にお問い合わせください。<br>
          下記のプライバシーポリシーをご確認の上、送信してください。<br>
          <span class="message-notice"><span class="essential">必須</span>は必須項目です。</span>
        </p>
        <p class="text notosan">
          <a href="#">送信にあたっては、こちらのプライバシーポリシーをご覧ください。</a>
        </p>
          <div class="contact-inner">
            <form action="{{ url_for('contact_complete' )}}" method="POST" novalidate="novalidate">
              <table class="contact-table">
                <tr class="table-list">
                  <th>
                    <label for="name">お名前<span class="essential">必須</span></label>
                  </th>
                  <td>
                    <input type="text" name="username" value="{{ username }}" placeholder="お名前をご入力ください" class="input-area" autocomplete="name">
                  </td>
                </tr>
                <tr class="table-list">
                  <th>
                    <label for="mail">メールアドレス<span class="essential">必須</span></label>
                  </th>
                  <td>
                    <input type="text" name="email" value="{{ email }}" placeholder="メールアドレスをご入力ください" class="input-area" autocomplete="email">
                  </td>
                </tr>
                <tr class="table-list">
                  <th>
                    <label for="description">お問い合わせ内容<span class="essential">必須</span></label>
                  </th>
                  <td>
                    <textarea id="description" name="description">{{ description }}</textarea>
                  </td>
                </tr>
              </table>
              {% with messages = get_flashed_messages() %}
              {% if messages %}
              <ul>
                {% for message in messages %}
                <li>{{ message }}</li>
                {% endfor %}
              </ul>
              {% endif %}
              {% endwith %}
              <input type="submit" value="送信する" class="submit-button">
            </form>
          </div>
      </div>
    </div>
  </body>
</html>
問い合わせ内容のテキストエリアと送信ボタンの間にFlashメッセージが出現します。(メール送信時)
ロギング(logging)を利用する
開発時や運用時に予期しないエラーが発生する場合など、アプリで何が発生しているか確認したいケースがあります。
上記の場合にロギング(loggingモジュール)が利用できます。
ロガー(logger)にはログレベルが存在し、指定したログレベルよりもレベルが高いログだけ出力できます。
| レベル | 概要 | 説明 | 
|---|---|---|
| CRITICAL | 致命的なエラー | プログラム異常終了を伴うエラー情報 | 
| ERROR | エラー | 予期しない実行時エラー情報 | 
| WARNING | 警告 | エラーに近い減少などの準正常系情報 | 
| INFO | 情報 | 正常動作の確認に必要な情報 | 
| DEBUG | デバッグ情報 | 開発時に必要な情報 | 
例えば、開発時であればDEBUG、本番環境であればERRORなど、環境によってログレベルを切り替えるイメージです。
ログレベルは、app.pyにapp.logger.setLevel関数を利用し指定します。
# Flashの追加
from flask import Flask, render_template, url_for, request, redirect, flash
# email_validatorをインポート
from email_validator import validate_email, EmailNotValidError
# loggingをインポート
import logging
# Flaskクラスをインスタンス化
app = Flask(__name__)
# SECRET_KEYを追加する
app.config["SECRET_KEY"] = "生成したパスワード"
# ログレベルの設定
app.logger.setLevel(logging.DEBUG)
# URLと実行する関数をマッピング
@app.route("/contact", methods=["GET"])
def contact():
    return render_template("contact.html")
@app.route("/contact/complete", methods=["GET", "POST"])
def contact_complete():
    if request.method == "POST":
        # form属性から値を取得する
        username = request.form["username"]
        email = request.form["email"]
        description = request.form["description"]
        
        # バリデーションチェック追加
        is_valid = True
        if not username:
            flash("ユーザー名は必須です")
            is_valid = False
        if not email:
            flash("メールアドレスは必須です")
            is_valid = False
        try:
            validate_email(email)
        except EmailNotValidError:
            flash("メールアドレス形式で入力してください")
            is_valid = False
        if not description:
            flash("問い合わせ内容は必須です")
            is_valid = False
        if not is_valid:
            return redirect(url_for("contact"))
        
        # contact_completeエンドポイントへリダイレクト
        return redirect(url_for("contact_complete"))
    
    return render_template("contact_complete.html")ログを出力するには以下のように指定します。
app.logger.ciritical("fatal error")
app.logger.error("error")
app.logger.warning("warning")
app.logger.info("info")
app.logger.debug("debug")flask-debugtoolbarのインストール
Flaskで開発する際、デバッグ用モジュールとしてflask-debugtoolbarが利用できます。
flask-debugtoolbarは以下のコマンドでインストールします。
pip install flask-debugtoolbar# Flashの追加
from flask import Flask, render_template, url_for, request, redirect, flash
# email_validatorをインポート
from email_validator import validate_email, EmailNotValidError
# flask-debugtoolbarをインポート
from flask_debugtoolbar import DebugToolbarExtension
# loggingをインポート
import logging
# Flaskクラスをインスタンス化
app = Flask(__name__)
# SECRET_KEYを追加する
app.config["SECRET_KEY"] = "生成したパスワード"
# ログレベルの設定
app.logger.setLevel(logging.DEBUG)
# リダイレクトを中断しないように設定
app.config["DEBUG_TB_INTERCEPT_REDIRECTS"] = False
# DebugToolbarExtensionにアプリケーションをセット
toolbar = DebugToolbarExtension(app)
# URLと実行する関数をマッピング
@app.route("/contact", methods=["GET"])
def contact():
    return render_template("contact.html")
@app.route("/contact/complete", methods=["GET", "POST"])
def contact_complete():
    if request.method == "POST":
        # form属性から値を取得する
        username = request.form["username"]
        email = request.form["email"]
        description = request.form["description"]
        
        # バリデーションチェック追加
        is_valid = True
        if not username:
            flash("ユーザー名は必須です")
            is_valid = False
        if not email:
            flash("メールアドレスは必須です")
            is_valid = False
        try:
            validate_email(email)
        except EmailNotValidError:
            flash("メールアドレス形式で入力してください")
            is_valid = False
        if not description:
            flash("問い合わせ内容は必須です")
            is_valid = False
        if not is_valid:
            return redirect(url_for("contact"))
        
        # contact_completeエンドポイントへリダイレクト
        return redirect(url_for("contact_complete"))
    
    return render_template("contact_complete.html")ブラウザでURLにアクセスすると、ブラウザの右側にデバッグツールバーが表示されます。
メール(flask-mail)を送信する
contact-form画面から問い合わせした際に、メールを送信する機能を実装します。
FlaskのFlask-mailモジュールをインストールします。
pip install flask-mailflask-mailをインストールしたらコンフィグを設定します。
| 設定 | デフォルト値 | 説明 | 
|---|---|---|
| MAIL_SERVER | localhost | メールサーバーのホスト名 | 
| MAIL_POST | 25 | メールサーバーのポート番号 | 
| MAIL_USE_TLS | False | TLSを有効にするか | 
| MAIL_USE_SSL | False | SSLを有効にするか | 
| MAIL_DEBUG | app.debug | デバッグモード | 
| MAIL_USERNAME | None | 送信元メールアドレス | 
| MAIL_PASSWORD | None | 送信元メールアドレスのパスワード | 
| MAIL_DEFAULT_SENDER | None | メールの送信者名およびメールアドレス | 
Gmailにてアプリからメールの送信準備
Gmailを利用してアプリからメールを送信するには、以下の2段階認証プロセスページで設定します。
以下のアプリパスワードページでアプリ用のパスワードを取得する必要があります。
生成できたパスワードは、MAIL_PASSWORDに利用します。
メール送信機能を実装する
ここからメール送信機能を実装するために、以下の工程を踏んでいきます。
- flask-mailを利用する
 - コンフィグの設定値を追加する
 - メールを送信する
 - メールテンプレートの作成
 
flask-mailを利用する
ここからメール送信の機能を実装します。
# Flashの追加
from flask import Flask, render_template, url_for, request, redirect, flash
# email_validatorをインポート
from email_validator import validate_email, EmailNotValidError
# flask-debugtoolbarをインポート
from flask_debugtoolbar import DebugToolbarExtension
# flask-mailをインポート
from flask_mail import Mail
# loggingをインポート
import logging
# osをインポート
import os
# Flaskクラスをインスタンス化
app = Flask(__name__)
# SECRET_KEYを追加する
app.config["SECRET_KEY"] = "生成したパスワード"
# ログレベルの設定
app.logger.setLevel(logging.DEBUG)
# リダイレクトを中断しないように設定
app.config["DEBUG_TB_INTERCEPT_REDIRECTS"] = False
# DebugToolbarExtensionにアプリケーションをセット
toolbar = DebugToolbarExtension(app)
# Mailクラスのコンフィグ設定
app.config["MAIL_SERVER"] = os.environ.get("MAIL_SERVER")
app.config["MAIL_PORT"] = os.environ.get("MAIL_PORT")
app.config["MAIL_USE_TLS"] = os.environ.get("MAIL_USE_TLS")
app.config["MAIL_USERNAME"] = os.environ.get("MAIL_USERNAME")
app.config["MAIL_PASSWORD"] = os.environ.get("MAIL_PASSWORD")
app.config["MAIL_DEFAULT_SENDER"] = os.environ.get("MAIL_DEFAULT_SENDER")
# flask-mailを登録する
mail = Mail(app)
# URLと実行する関数をマッピング
@app.route("/contact", methods=["GET"])
def contact():
    return render_template("contact.html")
@app.route("/contact/complete", methods=["GET", "POST"])
def contact_complete():
    if request.method == "POST":
        # form属性から値を取得する
        username = request.form["username"]
        email = request.form["email"]
        description = request.form["description"]
        
        # バリデーションチェック追加
        is_valid = True
        if not username:
            flash("ユーザー名は必須です")
            is_valid = False
        if not email:
            flash("メールアドレスは必須です")
            is_valid = False
        try:
            validate_email(email)
        except EmailNotValidError:
            flash("メールアドレス形式で入力してください")
            is_valid = False
        if not description:
            flash("問い合わせ内容は必須です")
            is_valid = False
        if not is_valid:
            return redirect(url_for("contact"))
        
        # contact_completeエンドポイントへリダイレクト
        return redirect(url_for("contact_complete"))
    
    return render_template("contact_complete.html")- 環境変数を取得するためにosをimport
 - Mailクラスをimport
 - Mailクラスのコンフィグを環境変数から取得
 - flask-mailをアプリに登録
 
コンフィグの設定値を追加する
次に、.envファイルにflask-mailコンフィグの設定値を追加します。
FLASK_APP = apps.minimalapp.app.py
FLASK_DEBUG = 1
# flask-mailのコンフィグ設定
MAIL_SERVER = smtp.gmail.com
MAIL_PORT = 587
MAIL_USE_TLS = True
MAIL_USERNAME = [Gmailメールアドレス]
MAIL_PASSWORD = [2段階認証で発行したパスワード]
MAIL_DEFAULT_SENDER = アプリ名 <Gmailメールアドレス>メールを送信する
次に、cotact_completeエンドポイントにメール送信する処理を追加します。
# Flashの追加
from flask import Flask, render_template, url_for, request, redirect, flash
# email_validatorをインポート
from email_validator import validate_email, EmailNotValidError
# flask-debugtoolbarをインポート
from flask_debugtoolbar import DebugToolbarExtension
# flask-mailをインポート(Messageを追加)
from flask_mail import Mail, Message
# loggingをインポート
import logging
# osをインポート
import os
# Flaskクラスをインスタンス化
app = Flask(__name__)
# SECRET_KEYを追加する
app.config["SECRET_KEY"] = "&ppphc6dM-Hp"
# ログレベルの設定
app.logger.setLevel(logging.DEBUG)
# リダイレクトを中断しないように設定
app.config["DEBUG_TB_INTERCEPT_REDIRECTS"] = False
# DebugToolbarExtensionにアプリケーションをセット
toolbar = DebugToolbarExtension(app)
# Mailクラスのコンフィグ設定
app.config["MAIL_SERVER"] = os.environ.get("MAIL_SERVER")
app.config["MAIL_PORT"] = os.environ.get("MAIL_PORT")
app.config["MAIL_USE_TLS"] = os.environ.get("MAIL_USE_TLS")
app.config["MAIL_USERNAME"] = os.environ.get("MAIL_USERNAME")
app.config["MAIL_PASSWORD"] = os.environ.get("MAIL_PASSWORD")
app.config["MAIL_DEFAULT_SENDER"] = os.environ.get("MAIL_DEFAULT_SENDER")
# flask-mailを登録する
mail = Mail(app)
# URLと実行する関数をマッピング
@app.route("/contact", methods=["GET"])
def contact():
    return render_template("contact.html")
@app.route("/contact/complete", methods=["GET", "POST"])
def contact_complete():
    if request.method == "POST":
        # form属性から値を取得する
        username = request.form["username"]
        email = request.form["email"]
        description = request.form["description"]
        
        # バリデーションチェック追加
        is_valid = True
        if not username:
            flash("ユーザー名は必須です")
            is_valid = False
        if not email:
            flash("メールアドレスは必須です")
            is_valid = False
        try:
            validate_email(email)
        except EmailNotValidError:
            flash("メールアドレス形式で入力してください")
            is_valid = False
        if not description:
            flash("問い合わせ内容は必須です")
            is_valid = False
        if not is_valid:
            return redirect(url_for("contact"))
        
        # メール送信
        send_email(
            email,
            "お問い合わせありがとうございました。",
            "contact_mail",
            username = username,
            description = description,
        )
        
        # contact_completeエンドポイントへリダイレクト
        return redirect(url_for("contact_complete"))
    
    return render_template("contact_complete.html")
# メール送信関数
def send_email(to, subject, template, **kwargs):
    msg = Message(subject, recipients=[to])
    msg.body = render_template(template + ".txt", **kwargs)
    msg.html = render_template(template + ".html", **kwargs)
    mail.send(msg)- flask-mailにてMessageを追加インポート
 - メール送信の処理としてメール送信関数を呼び出す
 - メール送信関数を追加
 
メールテンプレートの作成
次に、メールテンプレートを作成します。
templatesディレクトリにテキスト版のcontact_mail.txtとHTML版のcontact_mail.htmlに作成します。
{{ username }} 様
お問い合わせありがとうございます。
お問い合わせ内容はこちらになります。
▼お問い合わせ内容
{{ description }}<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>CONTACT-COMPLETE</title>
  </head>
  <body>
    <p>{{ username }} 様</p>
    <p>お問い合わせありがとうございます。</p>
    <p>お問い合わせ内容はこちらになります。</p>
    <p>お問い合わせ内容</p>
    <p>{{ description }}</p>
  </body>
</html>contact-formの動作確認
最後に、cotact-formが問題なく動作するか確認します。
任意のディレクトリにて、flask runコマンドを実行し/contactへアクセスしてください。
実際にメール送信を実行し送信できていれば完了です。

