Как создать форму обратной связи для сайта на облачных функциях

С помощью форм обратной связи на сайте можно организовать сбор отзывов от посетителей или приём заказов на услуги или товары.

Разберём пример HTML страницы и облачной функции для отправки содержимого формы обратной связи на электронную почту:

  • создание страницы с формой обратной связи;
  • добавление защиты от спама с помощью reCAPTCHA.

Простая форма обратной связи

В этом примере сделаем форму, в которой спросим у пользователя его имя, телефон и обратную связь, которой он хотел бы поделиться: проблемой или идеей для сайта.

HTML внешнего вида формы обратной связи будет выглядеть так:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Contact Us Form Example</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body>
  <div class="content container pb-5">
    <div class="row justify-content-md-center mt-5 pl-5">
      <div class="col-8 mb-3">
        <form class="needs-validation" id="mainForm">
          <div class="form-group">
            <label for="name_field">Name</label>
            <input name="name" type="text" class="form-control" id="name_field"
                   value="Mark" required>
          </div>
          <div class="form-group">
            <label for="phone_field">Phone</label>
            <input name="phone" type="text" class="form-control" id="phone_field"
                   placeholder="+9 999 999999" value="+7 999 888777" required>
          </div>
          <div class="form-group">
            <label for="desc_field">Description</label>
            <textarea class="form-control" name="desc" id="desc_field" rows="3"></textarea>
          </div>
          <button class="btn btn-primary" type="submit">Submit</button>
        </form>
      </div>
    </div>
  </div>
  <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
  <script>
    jQuery(function() {
      $('#mainForm').on('submit', function(e) {
        e.preventDefault();
        $.ajax({
          url: '<Место для ссылки на вашу облачную функцию>',
          type: 'POST',
          cache: false,
          data: $(e.target).serialize()
        }).done(function() {
          alert('Done.');
        }).fail(function() {
          alert('Something went wrong.');
        });
      });
    });
  </script>
</body>
</html>

Приведённый код есть в нашем github-репозитории.

В коде по событию «submit» отправляется AJAX POST-запрос в облачную функцию с содержимым веб-формы. Для этого подключена jQuery, хотя вместо неё может быть другая реализация отправки POST-запроса. Чтобы форма выглядела красиво, использован Bootstrap4, но оформление может быть любым.

Перейдём к облачной функции, которая будет отвечать за обработку полученных данных.

Содержимое формы можно сохранять в СУБД, в Файловое Хранилище, отправлять на электронную почту, в Telegram, в Slack, в свою CRM-систему и так далее — вариантов много. Чтобы решить задачу наиболее общим способом, на каждое обращение будет реализована отправка письма на указанную в настройках функции электронную почту.

Код такой функции будет выглядеть так:

import os
import json
import smtplib

EMAIL_HOST = os.environ.get('EMAIL_HOST', 'smtp.yandex.ru')
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 587))
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')

EMAIL_TO = os.environ.get('EMAIL_TO')


def main(**kwargs):
    text = json.dumps(kwargs, indent=2, ensure_ascii=False)
    print("Received: %s" % text)

    server = smtplib.SMTP(EMAIL_HOST, EMAIL_PORT)
    server.ehlo()
    server.starttls()
    server.login(EMAIL_HOST_USER, EMAIL_HOST_PASSWORD)
    message = "\r\n".join([
        f"From: {EMAIL_HOST_USER}",
        f"To: {EMAIL_TO}",
        "Subject: Serverless Form",
        "",
        str(text)
    ])
    server.set_debuglevel(1)
    server.sendmail(EMAIL_HOST_USER, EMAIL_TO, message)
    server.quit()
    return "Email was sent"

Приведённый код доступен в нашем github-репозитории.

Код превращает все полученные аргументы в форматированный JSON. Подключается к SMTP серверу и отправляет письмо в виде текста.

Здесь нужно задать некоторые настройки в переменных окружения, в том числе для подключения к SMTP-серверу, например, mail.google.com или mail.yandex.ru.

В итоге есть HTML форма, которая шлёт AJAX запрос в облачную функцию. При этом URL облачной функции «спрятан» внутри Javascript-кода намеренно, чтобы тривиальные crawler-боты не могли слать спам через эту форму, обнаружив в ней атрибут action.

Форма обратной связи с CAPTCHA

Чтобы отсеивать любых ботов, создадим форму обратной связи с CAPTCHA.

Чтобы добавить CAPTCHA на форму, воспользуемся сервисом reCAPTCHA v3.

Для этого нужно:

  1. Создать ключ в сервисе google.com/recaptcha.
  2. Переделать HTML из предыдущей главы в следующий:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Contact Us Form Example</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    </head>
    <body>
    <div class="content container pb-5">
    <div class="row justify-content-md-center mt-5 pl-5">
      <div class="col-8 mb-3">
        <form class="needs-validation" id="mainForm">
          <div class="form-group">
            <label for="name_field">Name</label>
            <input name="name" type="text" class="form-control" id="name_field"
                   value="Mark" required>
          </div>
          <div class="form-group">
            <label for="phone_field">Phone</label>
            <input name="phone" type="text" class="form-control" id="phone_field"
                   placeholder="+9 999 999999" value="+7 999 888777" required>
          </div>
          <div class="form-group">
            <label for="desc_field">Description</label>
            <textarea class="form-control" name="desc" id="desc_field" rows="3"></textarea>
          </div>
          <button class="btn btn-primary g-recaptcha"
                  data-sitekey="<Место для SITE KEY из reCAPTCHA>"
                  data-callback='onFeedbackFormSubmit'
                  data-action='submit'>Submit</button>
        </form>
      </div>
    </div>
    </div>
    <script src="https://www.google.com/recaptcha/api.js"></script>
    <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
    <script>
    function onFeedbackFormSubmit() {
      $.ajax({
        url: '<Место для ссылки на вашу облачную функцию>',
        type: 'POST',
        cache: false,
        data: $('#mainForm').serialize()
      }).done(function() {
        alert('Done.');
      }).fail(function() {
        alert('Something went wrong.');
      });
    }
    </script>
    </body>
    </html>

    Приведённый код доступен в нашем github-репозитории.

Таким способом можно защитить вызов облачной функции от crawler-ботов.

Для полной защиты нужно провалидировать результат проверки reCAPTCHA со стороны сервера. Это можно сделать так:

import os
import json
import smtplib
from urllib import request, parse

EMAIL_HOST = os.environ.get('EMAIL_HOST', 'smtp.yandex.ru')
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 587))
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')

EMAIL_TO = os.environ.get('EMAIL_TO')

RECAPTCHA_SECRET_KEY = os.environ.get('RECAPTCHA_SECRET_KEY')

def is_captcha_challenge_succeed(response_token):
    data = parse.urlencode({
        'secret': RECAPTCHA_SECRET_KEY,
        'response': response_token,
    }).encode()
    req = request.Request('https://www.google.com/recaptcha/api/siteverify', data=data)
    resp_data = json.load(request.urlopen(req))
    return resp_data.get('success') or False

def format_email(**kwargs):
    return json.dumps(kwargs, indent=2, ensure_ascii=False)

def send_email(text):
    server = smtplib.SMTP(EMAIL_HOST, EMAIL_PORT)
    server.ehlo()
    server.starttls()
    server.login(EMAIL_HOST_USER, EMAIL_HOST_PASSWORD)
    message = "\r\n".join([
        f"From: {EMAIL_HOST_USER}",
        f"To: {EMAIL_TO}",
        "Subject: Serverless Form",
        "",
        str(text)
    ])
    server.set_debuglevel(1)
    server.sendmail(EMAIL_HOST_USER, EMAIL_TO, message)
    server.quit()

def main(**kwargs):
    if is_captcha_challenge_succeed(kwargs.pop('g-recaptcha-response')):
        text = format_email(**kwargs)
        print(f'Sending """{text}"""')
        send_email(text)
        return "Email was sent"
    return "CAPTCHA challenge failed"

Приведённый код доступен в нашем github-репозитории.

Реализована форма обратной связи и обеспечена полноценная защита от спам-ботов.