Buenas mi gente en este nuevo post aprenderemos a enviar email desde nuestro formulario de contacto; que hemos creado en el anterior. Va a ser bastante breve, pero muy entretenido seguramente!. Así que abrid vuestros editores, activar el entorno virtual y manos a la obra!!
Para este caso vamos a utilizar los servidores de Google y nuestra cuenta de Gmail. Así que necesitas una cuenta de este proveedor o bien puedes configurar la librería que vamos a utilizar con tu propio proveedor de servicio de correo; como podría también ser el clásico Hotmail.
Primero lo primero, veamos como había quedado nuestro formulario de contacto:
Claro que notas un error en el Captcha, pero es porque he eliminado las claves secretas de mi código. Porque a la hora de copiarlo y pegarlo aquí olvido quitarlo y luego podría tener problemas claro… Pero colocando las claves, funciona correctamente. Veamos como había quedado nuestro código tanto de la plantilla “contacto.html” y “contacto2.html” como de nuestro controlador “run.py“. Y recordemos también que habíamos creado dos formularios y dos formas distintas de procesarlos en sus respectivas rutas “/contacto” y “/contacto2“.
Código de “contacto.html“:
{% extends "base/base.html" %} {%block head%} <script src="https://www.google.com/recaptcha/api.js"></script> {%endblock%} {% block title %}Contacto{% endblock %} {%block columna8 %} <div class ="contenedor-neon-int"> <div class = "title-neon" > <h3>Enviar mensaje a través del espacio</h3> <p class="mb-4"></p> <p class = "p-neon">Seguro tenemos mucho de que hablar! Puedes contactarme a través del siguiente formulario:</p> <br> </div> <form action = "/contacto" method = "post" class = "form-horizontal"> <div class="form-group"> <div class="col-sm-10 ml-auto mr-auto"> <p>Tu Nombre:</p> <input type="name" name = "Nombre" class="form-control" placeholder="Nombre"> </div> </div> <div class="form-group"> <div class="col-sm-10 ml-auto mr-auto"> <p>Tu Email:</p> <input type="email" name = "Email" class="form-control" placeholder="Email"> </div> </div> <div class="form-group"> <div class="col-sm-10 ml-auto mr-auto"> <p>Tu mensaje:</p> <textarea name = "Mensaje" rows="5" cols="20" class="form-control" placeholder="Mensaje"></textarea> </div> </div> <div class="g-recaptcha" align = "center" data-sitekey="{{sitekey}}"></div> <div class="col-sm-10 ml-auto mr-auto"> <br> <div align = "right"> <a href="#"><button type="submit"> <span></span> <span></span> <span></span> <span></span> ENVIAR MENSAJE! </button></a> </div> </div> <br> </div> </form> {%endblock %} {% block columna4 %} {% endblock %}
Código de “contacto2.html“:
{% extends "base/base.html" %} {% import "bootstrap/wtf.html" as wtf %} {%block head%} <script src="https://www.google.com/recaptcha/api.js"></script> {%endblock%} {% block title %}Contacto{% endblock %} {%block columna8 %} <div class ="contenedor-neon-int"> <div class = "title-neon" > <h3>Enviar mensaje a través del espacio</h3> <p class="mb-4"></p> <p class = "p-neon">Seguro tenemos mucho de que hablar! Puedes contactarme a través del siguiente formulario:</p> <br> </div> <!--Aquí estaba el formulario HTML anteriormente--> <div class="row"> <div class="col-sm-10 ml-auto mr-auto"> <form action = "/contacto2" method = "post" class = "form-horizontal"> {{ form.csrf_token() }} {{ wtf.form_field(form.name, class='form-control', placeholder='Nombre') }} {{ wtf.form_field(form.email, class='form-control', placeholder='Email') }} {{ wtf.form_field(form.message, class='form-control', placeholder='Tu mensaje') }} {{ recaptcha }} <div align = "right"> <a href="#"><button type="submit"> <span></span> <span></span> <span></span> <span></span> Enviar! </button></a> </div></form> </div></div> {%endblock %} {% block columna4 %} {% endblock %}
Código de “forms.py“:
#Importar from flask_wtf import FlaskForm #Aquí importamos, campodetexto, validadoresdedatos, y el boton submit from wtforms import StringField, validators, SubmitField #Aquí de los validadores importamos el datoobligatorio y el email from wtforms.validators import DataRequired, Email import email_validator class miformulario(FlaskForm): name = StringField(label='Nombre', validators=[DataRequired()]) email = StringField(label='Email', validators=[DataRequired(), Email(granular_message=True)]) message = StringField(label='Mensaje') submit = SubmitField(label="Enviar")
Código de “run.py“:
#Importar from flask import Flask, render_template, request import requests, json #Solución import requests from venv from forms import miformulario from flask_bootstrap import Bootstrap #importamos Bootstrap para el formulario from flask_recaptcha import ReCaptcha # Importar ReCaptcha #Crear app medante instancia app = Flask(__name__) #Configuraciones app.secret_key = "pythones.netelmejorblog" Bootstrap(app) #Decoramos nuestra app con bootstrap #Recaptcha app.config['RECAPTCHA_SITE_KEY'] = 'captcha' app.config['RECAPTCHA_SECRET_KEY'] = 'captcha' recaptcha = ReCaptcha(app) #Crear rutas con sus correspondientes funciones #INICIO @app.route('/', methods=['GET']) def index(): return render_template('/index.html') #MI BLOG @app.route('/blog', methods=['GET']) def blog(): return render_template('/blog.html') #MIS PROYECTOS @app.route('/mis-proyectos', methods=['GET']) def mostrarproyectos(): return render_template('mis-proyectos.html') #MIS HABILIDADES @app.route('/habilidades', methods=['GET']) def habilidades(): return render_template('mis-habilidades.html') #CONTACTO @app.route("/contacto", methods=["GET", "POST"]) def contacto(): sitekey = "captcha" if request.method == "POST": name = request.form['Nombre'] email = request.form['Email'] Mensaje = request.form['Mensaje'] respuesta_del_captcha = request.form['g-recaptcha-response'] if comprobar_humano(respuesta_del_captcha): #Si devuelve True status = "Exito." print (status) else: #Si devuelve False status = "Error, vuelve a comprobar que no eres un robot." print (status) return render_template("contacto.html", sitekey=sitekey) #CONTACTO2 - FLASKFORMS WTFORMS @app.route("/contacto2", methods=["GET", "POST"]) def contacto2(): miform = miformulario() if miform.validate_on_submit() and recaptcha.verify(): print(f"Name:{miform.name.data},Email:{miform.email.data},message:{miform.message.data}") else: print("Algún dato es invalido") return render_template("contacto2.html", form=miform) def comprobar_humano(respuesta_del_captcha): secret = "captcha" payload = {'response':respuesta_del_captcha, 'secret':secret} response = requests.post("https://www.google.com/recaptcha/api/siteverify", payload) response_text = json.loads(response.text) return response_text['success'] #Ejecutar nuestra app cuando ejecutemos este archivo run.py if __name__ == '__main__': app.run(debug=True)
Instalar Flask-Mail para enviar email’s
Para enviar un mail utilizando flask vamos a usar una librería llamada Flask-Mail y la vamos a configurar de manera que podamos utilizar el servidor de Gmail. Entonces al momento que nuestro visitante complete el formulario para contactarnos se enviará una solicitud al servidor de Gmail para que nos envíe un correo a nuestra cuenta de mail personal, pero utilizando nuestra cuenta de Gmail. Puede o no ser la misma cuenta que envía y recibe el mensaje, lo bueno será que nos quedará el mail que haya completado nuestro visitante y luego podremos responderle a su casilla de correo.
En definitiva lo que intento explicar es que puedes utilizar solo una cuenta de Mail y será como enviarte un mail a ti mismo. O también puedes utilizar dos cuentas diferentes, una para enviar los correos y otra para recibirlos.
Comencemos activando nuestro entorno virtual e instalando flask-mail.
pip install flask-mail
Ahora como siempre, simplemente debemos importarlo en nuestro controlador “run.py“:
from flask_mail import Mail, Message
Configurar Flask-Mail
Normalmente en nuestro proyecto deberíamos tener un archivo de configuración donde son alojadas todas las configuraciones de nuestro proyecto. Pero como nosotros estamos empezando recientemente con FLASK lo haremos directamente en el archivo “run.py” y ya luego aprenderemos a organizar un proyecto FLASK. Así que en nuestro archivo “run.py” debemos añadir las siguientes configuraciones:
app.config['MAIL_SERVER']='smtp.gmail.com' app.config['MAIL_PORT'] = 465 app.config['MAIL_USERNAME'] = 'yourId@gmail.com' app.config['MAIL_PASSWORD'] = '*****' app.config['MAIL_USE_TLS'] = False app.config['MAIL_USE_SSL'] = True mail = Mail(app)
Estas configuraciones nos permiten definir el servidor de Mail y hacer login en el. Pero lo cierto es que para utilizar el servidor de Gmail primeramente debemos configurarlo para admitir este tipo de solicitudes de nuestra aplicación FLASK. De lo contrario nos encontraremos con algunas trabas de seguridad que Gmail tomará como medida preventiva.. Así que vamos a configurar nuestra cuenta de email en Gmail:
Configurar nuestra cuenta Gmail para admitir solicitudes externas
Aunque nuestro usuario / password de cuenta de Gmail sean validos el servidor de Gmail nos va a rechazar la sesión debido a un mecanismo de seguridad. Para ello debemos ir primero a:
https://support.google.com/accounts/answer/185839
Donde obtendremos información acerca de activar “verificación en dos pasos” que es requerida para poder activar la opción de logearse desde app’s externas a Google. Y una vez activada dicha verificación debemos establecer una contraseña para utilizar app’s externas:
Así que primero debes ir a tu Cuenta de google>Seguridad:
Luego debemos hacer clic en “Contraseñas de aplicaciones” y establecer nuestra app creando automáticamente una contraseña para su acceso:
Y así Google nos otorga una “contraseña de 16 dígitos” que podemos usar en nuestra App en el apartado de configuración de Flask-Mail. Utilizarás esta contraseña y no la tradicional que utilizas para iniciar sesión en Gmail!
Aquí en StackOverflow puedes leer más acerca de este método de seguridad de google.
En mi caso añadí la configuración de Flask-Mail debajo de la del Google ReCaptcha; mi “run.py” quedó así:
#Importar from flask import Flask, render_template, request import requests, json #Solución import requests from venv from forms import miformulario from flask_bootstrap import Bootstrap #importamos Bootstrap para el formulario from flask_recaptcha import ReCaptcha # Importar ReCaptcha from flask_mail import Mail, Message #Crear app medante instancia app = Flask(__name__) #Configuraciones app.secret_key = "pythones.netelmejorblog" Bootstrap(app) #Decoramos nuestra app con bootstrap #Recaptcha app.config['RECAPTCHA_SITE_KEY'] = 'captcha' app.config['RECAPTCHA_SECRET_KEY'] = 'captcha' recaptcha = ReCaptcha(app) #Flask-Mail app.config['MAIL_SERVER']='smtp.gmail.com' app.config['MAIL_PORT'] = 465 app.config['MAIL_USERNAME'] = 'yourId@gmail.com' app.config['MAIL_PASSWORD'] = '*****' app.config['MAIL_USE_TLS'] = False app.config['MAIL_USE_SSL'] = True mail = Mail(app) #Crear rutas con sus correspondientes funciones #INICIO @app.route('/', methods=['GET']) def index(): return render_template('/index.html') #MI BLOG @app.route('/blog', methods=['GET']) def blog(): return render_template('/blog.html') #MIS PROYECTOS @app.route('/mis-proyectos', methods=['GET']) def mostrarproyectos(): return render_template('mis-proyectos.html') #MIS HABILIDADES @app.route('/habilidades', methods=['GET']) def habilidades(): return render_template('mis-habilidades.html') #CONTACTO @app.route("/contacto", methods=["GET", "POST"]) def contacto(): sitekey = "captcha" if request.method == "POST": name = request.form['Nombre'] email = request.form['Email'] Mensaje = request.form['Mensaje'] respuesta_del_captcha = request.form['g-recaptcha-response'] if comprobar_humano(respuesta_del_captcha): #Si devuelve True status = "Exito." print (status) else: #Si devuelve False status = "Error, vuelve a comprobar que no eres un robot." print (status) return render_template("contacto.html", sitekey=sitekey) #CONTACTO2 - FLASKFORMS WTFORMS @app.route("/contacto2", methods=["GET", "POST"]) def contacto2(): miform = miformulario() if miform.validate_on_submit() and recaptcha.verify(): print(f"Name:{miform.name.data},Email:{miform.email.data},message:{miform.message.data}") else: print("Algún dato es invalido") return render_template("contacto2.html", form=miform) def comprobar_humano(respuesta_del_captcha): secret = "captcha" payload = {'response':respuesta_del_captcha, 'secret':secret} response = requests.post("https://www.google.com/recaptcha/api/siteverify", payload) response_text = json.loads(response.text) return response_text['success'] #Ejecutar nuestra app cuando ejecutemos este archivo run.py if __name__ == '__main__': app.run(debug=True)
Ahora es momento de ingresar nuestros datos. En el caso del servidor lo dejamos como está y añadimos nuestra cuenta y nuestra contraseña que acabamos de crear.. El resto quedará igual..
Añadir el código y enviar un email de prueba
Vamos a añadir el código necesario para enviar los datos que procesamos en nuestro formulario de contacto y enviarlo a través de un correo usando el servidor y nuestra de Google o Gmail al mismo u otro correo donde quieras recibir los mails que te envíen desde la sección contacto de tu aplicación.
En mi caso solo tengo una cuenta de correo Gmail y utilizaré la misma tanto para enviar como para recibir los correos.
Podemos hacerlo de dos formas, una sería creando una función aparte y llamándola desde nuestra función de las rutas contacto. O bien podríamos hacerlo directamente desde la ruta contacto. Pero voy a elegir la primera opción, así que en nuestro archivo “run.py” donde tenemos la función “comprobar_humano” vamos a crear la función “enviar_correo” y dentro de ella instanciaremos “Message” de la librería “flask-mail” para enviar un mensaje cuyo datos pasaremos mediante el parámetro “mensaje”:
def enviar_correo(mensaje): pass
Podemos leer la documentación de Flask-Mail para tener más datos acerca de como enviar un mail con esta librería. Creamos nuestra función y añadimos lo siguiente:
def enviar_correo(mensaje): msg = Message( subject = (f"{mensaje.get('Email')} quiere contactarse contigo desde tu app"), sender = mensaje.get('Email'), recipients = ['correo-donde-quieres-recibir@gmail.com'], body= mensaje.get('Mensaje') ) mail.send(msg)
Explicando un poco la función nuestra variable “msg” obtendrá del parámetro que pasamos en la función “mensaje” los datos “subject”, “Email” y “Mensaje”. Y estos nombres deben coincidir con los del formulario, además de ello he añadido el parámetro “subject” que no procesamos en nuestro formulario donde obtenemos el email de nuestro emisor.
Claro que nuestro parámetro “mensaje” de nuestra función enviar_correo debe ser una lista o diccionario que contenga estos datos, los cuales imaginas serán obtenidos del formulario y los pasaremos al llamar la función “enviar_correo“.
Así que ahora es momento de configurar nuestras funciones de “contacto” para enviar el mail en ambas rutas, recuerdas que teníamos dos “contacto” y “contacto2”.
#CONTACTO @app.route("/contacto", methods=["GET", "POST"]) def contacto(): sitekey = "clave-secreta-del-sitio-recaptcha" if request.method == "POST": name = request.form['Nombre'] email = request.form['Email'] Mensaje = request.form['Mensaje'] respuesta_del_captcha = request.form['g-recaptcha-response'] if comprobar_humano(respuesta_del_captcha): #Si devuelve True status = "Exito." enviar_correo(request.form) print (status) else: #Si devuelve False status = "Error, vuelve a comprobar que no eres un robot." print (status) return render_template("contacto.html", sitekey=sitekey)
Lo único que tenemos que hacer es llamar a nuestra función “enviar_correo” pasando como parámetros los datos del formulario usando la función “request“. Y esto lo hacemos dentro del condicional que comprueba si nuestro Captcha era correcto, claro. NO QUEREMOS QUE NOS ENVÍEN CORREOS SIN COMPROBAR EL CAPTCHA PRIMERO, SERIA UNA VULNERABILIDAD Y ESTAS PONIENDO EN JUEGO TU CUENTA DE GMAIL.
Ahora es momento de procesar los datos y enviar el correo usando nuestra función para comprobar si todo funciona correctamente. En este caso utilizaré primero la función y ruta “contacto” y luego lo haremos con “contacto2” donde utilizamos Flask-Forms o WTForms. ¿Recuerdas?
Hagamos un test, mi “run.py” quedó así:
#Importar from flask import Flask, render_template, request import requests, json #Solución import requests from venv from forms import miformulario from flask_bootstrap import Bootstrap #importamos Bootstrap para el formulario from flask_recaptcha import ReCaptcha # Importar ReCaptcha from flask_mail import Mail, Message #Crear app medante instancia app = Flask(__name__) #Configuraciones app.secret_key = "pythones.netelmejorblog" Bootstrap(app) #Decoramos nuestra app con bootstrap #Recaptcha app.config['RECAPTCHA_SITE_KEY'] = 'clave-del-sitio-google-recaptcha' app.config['RECAPTCHA_SECRET_KEY'] = 'tu-clave-secreta-recaptcha' recaptcha = ReCaptcha(app) #Flask-Mail app.config['MAIL_SERVER']='smtp.gmail.com' app.config['MAIL_PORT'] = 465 app.config['MAIL_USERNAME'] = 'tu-correo-que-va-a-enviar@gmail.com' app.config['MAIL_PASSWORD'] = 'password-obtenido-de-google-en-el-paso-primero' app.config['MAIL_USE_TLS'] = False app.config['MAIL_USE_SSL'] = True mail = Mail(app) #Crear rutas con sus correspondientes funciones #INICIO @app.route('/', methods=['GET']) def index(): return render_template('/index.html') #MI BLOG @app.route('/blog', methods=['GET']) def blog(): return render_template('/blog.html') #MIS PROYECTOS @app.route('/mis-proyectos', methods=['GET']) def mostrarproyectos(): return render_template('mis-proyectos.html') #MIS HABILIDADES @app.route('/habilidades', methods=['GET']) def habilidades(): return render_template('mis-habilidades.html') #CONTACTO @app.route("/contacto", methods=["GET", "POST"]) def contacto(): sitekey = "clave-del-sitio-google-recaptcha" if request.method == "POST": name = request.form['Nombre'] email = request.form['Email'] Mensaje = request.form['Mensaje'] respuesta_del_captcha = request.form['g-recaptcha-response'] if comprobar_humano(respuesta_del_captcha): #Si devuelve True status = "Exito." enviar_correo(request.form) print (status) else: #Si devuelve False status = "Error, vuelve a comprobar que no eres un robot." print (status) return render_template("contacto.html", sitekey=sitekey) #CONTACTO2 - FLASKFORMS WTFORMS @app.route("/contacto2", methods=["GET", "POST"]) def contacto2(): miform = miformulario() if miform.validate_on_submit() and recaptcha.verify(): print(f"Name:{miform.name.data},Email:{miform.email.data},Mensaje:{miform.message.data}") else: print("Algún dato es invalido") return render_template("contacto2.html", form=miform) def comprobar_humano(respuesta_del_captcha): secret = "tu-clave-secreta-recaptcha" payload = {'response':respuesta_del_captcha, 'secret':secret} response = requests.post("https://www.google.com/recaptcha/api/siteverify", payload) response_text = json.loads(response.text) return response_text['success'] def enviar_correo(mensaje): msg = Message( subject = (f"{mensaje.get('Email')} quiere contactarse contigo desde tu app"), sender = mensaje.get('Email'), recipients = ['correo-donde-quieres-recibir@gmail.com'], body= mensaje.get('Mensaje') ) mail.send(msg) #Ejecutar nuestra app cuando ejecutemos este archivo run.py if __name__ == '__main__': app.run(debug=True)
Probando enviar un email desde nuestro formulario HTML
Por supuesto tu debes reemplazar algunos datos. Desde los de tu correo a los del ReCaptcha. Ahora fíjate:
Y Voilaa!. Se hizo la magia!
Ahora veamos como lo haríamos con la ruta “contacto2” donde aprendimos a usar WTForms.
Enviar un email desde formulario creado con WTForms o Flask-Forms
Recordemos que nuestra función creada para el formulario usando WTForms era mucho más corta y sencilla, de hecho comprobamos todos los datos en un único condicional que comprueba que estén ingresado los datos requeridos por el formulario a la vez que también comprueba que el ReCaptcha sea correcto:
#CONTACTO2 - FLASKFORMS WTFORMS @app.route("/contacto2", methods=["GET", "POST"]) def contacto2(): miform = miformulario() if miform.validate_on_submit() and recaptcha.verify(): print(f"Name:{miform.name.data},Email:{miform.email.data},Mensaje:{miform.message.data}") else: print("Algún dato es invalido") return render_template("contacto2.html", form=miform)
En el caso de utilizar WTForms nos encontraremos un poco más complicados para utilizar la misma función “enviar_correo“, ya que si solamente llamáramos a la función pasando como parámetro los datos de “miform” obtendríamos diversos errores. Pero como sabemos la librería Flask-Mail admite como parámetro un diccionario que contiene los datos del mensaje y entre ellos los del emisor (sender), receptor, sujeto, etc.
Y en nuestra función “enviar_correo” trabajamos con el método “get“, ¿Lo recuerdas?. Hablo de .get(), ojo!!. No de método de solicitud GET heee!
Lo aprendimos cuando trabajamos con diccionarios y más te vale lo recuerdes!. Fíjate aquí: Métodos de diccionarios en Python
Así que simplemente debemos pasar los datos como un diccionario nuevo y colocar el mismo nombre de las claves que nuestra función enviar_correo requiere cuando utiliza “get“:
Así que simplemente lo que tenemos que hacer es pasarle a nuestra función “enviar_correo” un diccionario como parámetro con algunos de los datos obtenidos de “miform“!. Que gracias a nuestro doble condicional si añadimos código dentro sabremos que ya fueron comprobados y validos los datos antes de enviar el correo. Nos bastaría añadir algo así:
#CONTACTO2 - FLASKFORMS WTFORMS @app.route("/contacto2", methods=["GET", "POST"]) def contacto2(): miform = miformulario() if miform.validate_on_submit() and recaptcha.verify(): print(f"Name:{miform.name.data},Email:{miform.email.data},Mensaje:{miform.message.data}") mimensaje = { 'Email': miform.email.data, 'Mensaje' : miform.message.data } enviar_correo(mimensaje) else: print("Algún dato es invalido") return render_template("contacto2.html", form=miform)
¿Y por qué solo añadimos el mail y el mensaje?. Porque el resto de datos ya los tienes en la función enviar correo!
Así que solo son necesarios esos dos argumentos enviados en forma de diccionario como parámetro para nuestra función. ¿Entiendes?
Fíjate en esto:
Creo que se sobre entiende, que estamos pasando como parámetro un diccionario para que nuestra función tal como estaba con el método .get() obtenga los datos de las claves que son estrictamente necesarios. Ya que los demás están definidos en la misma función enviar_correo(). Y pues, esto ha sido todo veamos que funciona, claro que funciona!:
Probando enviar un email desde nuestro formulario Creado con WTForms o Flask-Forms
Aquí accedo mediante la ruta “/contacto2” porque recuerda que es un formulario alternativo, tu puedes usar el que prefieras.
Bien hasta aquí llegamos hoy!!!.
Mis saludos y nos veremos pronto, no olvidéis compartir mi blog para llegar a mas personas interesadas en Python!
Saludos,
Me da un error al enviar el mensaje.
Intenta conectarse y al momento da el error.
KeyError: ‘mail’
Hola tienes otro detalle del error?. Probablemente esté relacionado con la devolución de algun argumento en forma de diccionario donde recibe un valor invalido. O bien puede tratarse de ReCaptcha.. Necesitaría ver tu código o saber si estás usando exactamente el del blog! Si quieres ayuda puedes enviarme un mensaje personal o contactarte desde el chat!. Saludos gracias por leer el blog!