{"id":4840,"date":"2022-07-14T14:26:04","date_gmt":"2022-07-14T14:26:04","guid":{"rendered":"https:\/\/codevoweb.com\/?p=4840"},"modified":"2023-05-05T20:23:56","modified_gmt":"2023-05-05T20:23:56","slug":"restful-api-with-python-fastapi-send-html-emails","status":"publish","type":"post","link":"https:\/\/codevoweb.com\/restful-api-with-python-fastapi-send-html-emails\/","title":{"rendered":"RESTful API with Python &#038; FastAPI: Send HTML Emails"},"content":{"rendered":"\n<p>In this article, you&#8217;ll learn how to send HTML emails with Python, FastAPI, PostgreSQL, <a href=\"https:\/\/www.fullstackpython.com\/jinja2.html\" target=\"_blank\" rel=\"noreferrer noopener\">Jinja2<\/a>, and Docker-compose. Also, you&#8217;ll learn how to dynamically generate HTML templates with the <a href=\"https:\/\/www.fullstackpython.com\/jinja2.html\" target=\"_blank\" rel=\"noreferrer noopener\">Jinja2<\/a> package.<\/p>\n\n\n\n<p>FastAPI is a modern, lightweight, high-performance, Python web framework tailored specifically for building APIs. It was designed to perform at par with popular backend frameworks like Express &amp; Fastify for Node.js, and Gin Gonic for Golang.<\/p>\n\n\n\n<p>API with Python and FastAPI Series:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"\/restful-api-with-python-fastapi-access-and-refresh-tokens\">RESTful API with Python &amp; FastAPI: Access and Refresh Tokens<\/a><\/li>\n\n\n\n<li><a href=\"\/restful-api-with-python-fastapi-send-html-emails\">RESTful API with Python &amp; FastAPI: Send HTML Emails<\/a><\/li>\n\n\n\n<li><a href=\"\/crud-restful-api-server-with-python-fastapi-and-postgresql\">CRUD RESTful API Server with Python, FastAPI, and PostgreSQL<\/a><\/li>\n<\/ol>\n\n\n\n<p>Related Articles:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/golang-grpc-server-and-client-signup-user-verify-email\">Build Golang gRPC Server and Client: SignUp User &amp; Verify Email<\/a><\/li>\n\n\n\n<li><a href=\"\/crud-api-node-js-and-postgresql-send-html-emails\">CRUD API with Node.js and PostgreSQL: Send HTML Emails<\/a><\/li>\n\n\n\n<li><a href=\"\/api-golang-mongodb-send-html-emails-gomail\">API with Golang + MongoDB: Send HTML Emails with Gomail<\/a><\/li>\n\n\n\n<li><a href=\"\/api-node-postgresql-typeorm-send-emails\">API with Node.js + PostgreSQL + TypeORM: Send Emails<\/a><\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"850\" height=\"478\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/RESTful-API-with-Python-FastAPI-Send-HTML-Emails.webp\" alt=\"RESTful API with Python &amp; FastAPI Send HTML Emails\" class=\"wp-image-4881\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/RESTful-API-with-Python-FastAPI-Send-HTML-Emails.webp 850w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/RESTful-API-with-Python-FastAPI-Send-HTML-Emails-300x169.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/RESTful-API-with-Python-FastAPI-Send-HTML-Emails-768x432.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/RESTful-API-with-Python-FastAPI-Send-HTML-Emails-100x56.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/RESTful-API-with-Python-FastAPI-Send-HTML-Emails-700x394.webp 700w\" sizes=\"auto, (max-width: 850px) 100vw, 850px\" \/><\/figure>\n\n\n<style>.kb-table-of-content-nav.kb-table-of-content-id_a6e137-36 .kb-table-of-content-wrap{padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}.kb-table-of-content-nav.kb-table-of-content-id_a6e137-36 .kb-table-of-contents-title-wrap{padding-top:0px;padding-right:0px;padding-bottom:0px;padding-left:0px;}.kb-table-of-content-nav.kb-table-of-content-id_a6e137-36 .kb-table-of-contents-title-wrap{color:#ffffff;}.kb-table-of-content-nav.kb-table-of-content-id_a6e137-36 .kb-table-of-contents-title{color:#ffffff;font-weight:regular;font-style:normal;}.kb-table-of-content-nav.kb-table-of-content-id_a6e137-36 .kb-table-of-content-wrap .kb-table-of-content-list{color:#ffffff;font-weight:regular;font-style:normal;margin-top:10px;margin-right:0px;margin-bottom:0px;margin-left:-5px;}@media all and (max-width: 1024px){.kb-table-of-content-nav.kb-table-of-content-id_a6e137-36 .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}@media all and (max-width: 767px){.kb-table-of-content-nav.kb-table-of-content-id_a6e137-36 .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}<\/style>\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p>Before you begin, you should:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Have fundamental knowledge of Python<\/li>\n\n\n\n<li>Have Python <strong>3.6+<\/strong> installed on your system<\/li>\n\n\n\n<li>Have Docker installed on your machine<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Send HTML Email with jinja2 and FastAPI Overview<\/h2>\n\n\n\n<span id=\"ezoic-pub-video-placeholder-107\"><\/span>\n\n\n\n<p>Here a user inputs the required credentials to register for an account<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"863\" height=\"1024\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/registration-form-with-no-validation-errors-react-hook-form-and-zod-863x1024.webp\" alt=\"registration form with no validation errors react hook form and zod\" class=\"wp-image-1370\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/registration-form-with-no-validation-errors-react-hook-form-and-zod-863x1024.webp 863w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/registration-form-with-no-validation-errors-react-hook-form-and-zod-253x300.webp 253w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/registration-form-with-no-validation-errors-react-hook-form-and-zod-768x912.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/registration-form-with-no-validation-errors-react-hook-form-and-zod-84x100.webp 84w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/registration-form-with-no-validation-errors-react-hook-form-and-zod-379x450.webp 379w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/registration-form-with-no-validation-errors-react-hook-form-and-zod-600x712.webp 600w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/registration-form-with-no-validation-errors-react-hook-form-and-zod.webp 904w\" sizes=\"auto, (max-width: 863px) 100vw, 863px\" \/><\/figure>\n\n\n\n<p>The FastAPI server validates the provided credentials, adds the user to the database, generates the Email verification template, and sends the verification code to the user&#8217;s email.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"955\" height=\"1001\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verification-page.png\" alt=\"API-with-Node.js-PostgreSQL-TypeORM-email-verification-page\" class=\"wp-image-1589\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verification-page.png 955w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verification-page-286x300.png 286w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verification-page-768x805.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verification-page-95x100.png 95w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verification-page-429x450.png 429w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verification-page-600x629.png 600w\" sizes=\"auto, (max-width: 955px) 100vw, 955px\" \/><\/figure>\n\n\n\n<p>The user then opens the email after receiving the success message from the FastAPI server.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"765\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/email-sent-by-gomail-golang-1024x765.png\" alt=\"email sent by gomail golang\" class=\"wp-image-1796\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/email-sent-by-gomail-golang-1024x765.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/email-sent-by-gomail-golang-300x224.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/email-sent-by-gomail-golang-768x574.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/email-sent-by-gomail-golang-100x75.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/email-sent-by-gomail-golang-602x450.png 602w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/email-sent-by-gomail-golang-600x448.png 600w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/email-sent-by-gomail-golang.png 1425w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>After the user clicks the &#8220;<strong>Verify Your Account<\/strong>&#8221; button, the user is redirected to the email verification page where the code is pre-filled in the input field.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"958\" height=\"1025\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-send-verification-code.png\" alt=\"API with Node.js + PostgreSQL + TypeORM send verification code\" class=\"wp-image-1587\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-send-verification-code.png 958w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-send-verification-code-280x300.png 280w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-send-verification-code-768x822.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-send-verification-code-93x100.png 93w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-send-verification-code-421x450.png 421w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-send-verification-code-600x642.png 600w\" sizes=\"auto, (max-width: 958px) 100vw, 958px\" \/><\/figure>\n\n\n\n<p>When the user clicks on the &#8220;<strong>VERIFY EMAIL<\/strong>&#8221; button, a <strong>GET<\/strong> request is made with the verification code to the FastAPI server.<\/p>\n\n\n\n<p>The FastAPI server verifies the code and updates the verified field in the database to <code>true<\/code> .<\/p>\n\n\n\n<p>After the verification process is completed, FastAPI returns a success message to the client, and the user is redirected to the login page.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"1013\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verified.png\" alt=\"API with Node.js + PostgreSQL + TypeORM email verified\" class=\"wp-image-1586\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verified.png 960w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verified-284x300.png 284w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verified-768x810.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verified-95x100.png 95w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verified-426x450.png 426w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/API-with-Node.js-PostgreSQL-TypeORM-email-verified-600x633.png 600w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Creating an SMTP Provider Account<\/h2>\n\n\n\n<p>If you are building a production-ready application, I will recommend you to use a popular SMTP provider (<a href=\"https:\/\/trial.smtp.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">SMTP.com<\/a>, <a href=\"https:\/\/sendgrid.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">SendGrid<\/a>, <a href=\"https:\/\/www.mailgun.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Mailgun<\/a>, etc) to send real emails to your users. <\/p>\n\n\n\n<p>However, since we are in a development environment, I&#8217;m going to use <a href=\"https:\/\/mailtrap.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Mailtrap<\/a> to capture the development emails.<\/p>\n\n\n\n<p>Follow the steps below to create and set up a <a href=\"https:\/\/mailtrap.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Mailtrap<\/a> account:<\/p>\n\n\n\n<p><strong>Step 1:<\/strong> Go to <a href=\"https:\/\/mailtrap.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Mailtrap<\/a> and create a new account<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"709\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/create-mailtrap-account-for-golang-email-1024x709.png\" alt=\"create mailtrap account for golang email\" class=\"wp-image-1790\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/create-mailtrap-account-for-golang-email-1024x709.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/create-mailtrap-account-for-golang-email-300x208.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/create-mailtrap-account-for-golang-email-768x532.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/create-mailtrap-account-for-golang-email-100x69.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/create-mailtrap-account-for-golang-email-650x450.png 650w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/create-mailtrap-account-for-golang-email-600x415.png 600w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/create-mailtrap-account-for-golang-email.png 1190w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><strong>Step 2:<\/strong> Sign in to your account and click on the &#8220;<strong>Add Inbox<\/strong>&#8221; button. Give the inbox a name and click on the &#8220;<strong>Save<\/strong>&#8221; button.<\/p>\n\n\n\n<p>Next, click on the Settings icon under the &#8220;<strong>Action<\/strong>&#8221; section to display the credentials page.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"993\" height=\"680\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/mailtrap-fastapi-python-click-settings.png\" alt=\"mailtrap fastapi python click settings\" class=\"wp-image-4849\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/mailtrap-fastapi-python-click-settings.png 993w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/mailtrap-fastapi-python-click-settings-300x205.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/mailtrap-fastapi-python-click-settings-768x526.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/mailtrap-fastapi-python-click-settings-100x68.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/mailtrap-fastapi-python-click-settings-657x450.png 657w\" sizes=\"auto, (max-width: 993px) 100vw, 993px\" \/><\/figure>\n\n\n\n<p>On the settings screen, click on the  <strong>&#8220;Show Credentials&#8221;<\/strong> dropdown link to display the SMTP credentials.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"875\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/mailtrap-fastapi-python-smtp-credentials_1-1024x875.png\" alt=\"mailtrap fastapi python smtp credentials_1\" class=\"wp-image-4851\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/mailtrap-fastapi-python-smtp-credentials_1-1024x875.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/mailtrap-fastapi-python-smtp-credentials_1-300x256.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/mailtrap-fastapi-python-smtp-credentials_1-768x656.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/mailtrap-fastapi-python-smtp-credentials_1-100x85.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/mailtrap-fastapi-python-smtp-credentials_1-527x450.png 527w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/mailtrap-fastapi-python-smtp-credentials_1.png 1028w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Update the Environment Variables File<\/h2>\n\n\n\n<p>Copy the SMTP credentials and add them to the <code>.env<\/code> file.<\/p>\n\n\n\n<p><strong>.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nEMAIL_HOST=smtp.mailtrap.io\nEMAIL_PORT=587\nEMAIL_USERNAME=4aeca0c9318dd2\nEMAIL_PASSWORD=a987a0e0eac00d\nEMAIL_FROM=admin@admin.com\n<\/code>\n<\/pre>\n\n\n\n<p>After adding the SMTP credentials, your <code>.env<\/code> file you look like this:<\/p>\n\n\n\n<p><strong>.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nDATABASE_PORT=6500\nPOSTGRES_PASSWORD=password123\nPOSTGRES_USER=postgres\nPOSTGRES_DB=fastapi\nPOSTGRES_HOST=postgres\nPOSTGRES_HOSTNAME=127.0.0.1\n\nACCESS_TOKEN_EXPIRES_IN=15\nREFRESH_TOKEN_EXPIRES_IN=60\nJWT_ALGORITHM=RS256\n\nCLIENT_ORIGIN=http:\/\/localhost:3000\n\nEMAIL_HOST=smtp.mailtrap.io\nEMAIL_PORT=587\nEMAIL_USERNAME=4aeca0c9318dd2\nEMAIL_PASSWORD=a987a0e0eac00d\nEMAIL_FROM=admin@admin.com\n\nJWT_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT2dJQkFBSkJBSSs3QnZUS0FWdHVQYzEzbEFkVk94TlVmcWxzMm1SVmlQWlJyVFpjd3l4RVhVRGpNaFZuCi9KVHRsd3h2a281T0pBQ1k3dVE0T09wODdiM3NOU3ZNd2xNQ0F3RUFBUUpBYm5LaENOQ0dOSFZGaHJPQ0RCU0IKdmZ2ckRWUzVpZXAwd2h2SGlBUEdjeWV6bjd0U2RweUZ0NEU0QTNXT3VQOXhqenNjTFZyb1pzRmVMUWlqT1JhUwp3UUloQU84MWl2b21iVGhjRkltTFZPbU16Vk52TGxWTW02WE5iS3B4bGh4TlpUTmhBaUVBbWRISlpGM3haWFE0Cm15QnNCeEhLQ3JqOTF6bVFxU0E4bHUvT1ZNTDNSak1DSVFEbDJxOUdtN0lMbS85b0EyaCtXdnZabGxZUlJPR3oKT21lV2lEclR5MUxaUVFJZ2ZGYUlaUWxMU0tkWjJvdXF4MHdwOWVEejBEWklLVzVWaSt6czdMZHRDdUVDSUVGYwo3d21VZ3pPblpzbnU1clBsTDJjZldLTGhFbWwrUVFzOCtkMFBGdXlnCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t\nJWT_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBSSs3QnZUS0FWdHVQYzEzbEFkVk94TlVmcWxzMm1SVgppUFpSclRaY3d5eEVYVURqTWhWbi9KVHRsd3h2a281T0pBQ1k3dVE0T09wODdiM3NOU3ZNd2xNQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Validating the Environment Variables<\/h2>\n\n\n\n<p>Now that we added the credentials to the <code>.env<\/code> file, let&#8217;s add the variables to the <code>app\/config.py<\/code> for <a href=\"https:\/\/pydantic-docs.helpmanual.io\/usage\/settings\/\" target=\"_blank\" rel=\"noreferrer noopener\">Pydantic<\/a> to load and validate them.<\/p>\n\n\n\n<p><strong>app\/config.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom pydantic import BaseSettings, EmailStr\n\n\nclass Settings(BaseSettings):\n    DATABASE_PORT: int\n    POSTGRES_PASSWORD: str\n    POSTGRES_USER: str\n    POSTGRES_DB: str\n    POSTGRES_HOST: str\n    POSTGRES_HOSTNAME: str\n\n    JWT_PUBLIC_KEY: str\n    JWT_PRIVATE_KEY: str\n    REFRESH_TOKEN_EXPIRES_IN: int\n    ACCESS_TOKEN_EXPIRES_IN: int\n    JWT_ALGORITHM: str\n\n    CLIENT_ORIGIN: str\n\n    EMAIL_HOST: str\n    EMAIL_PORT: int\n    EMAIL_USERNAME: str\n    EMAIL_PASSWORD: str\n    EMAIL_FROM: EmailStr\n\n    class Config:\n        env_file = '.\/.env'\n\n\nsettings = Settings()\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create a Database Model with Sqlalchemy<\/h2>\n\n\n\n<p>If you came from the previous article, update the user model to have the <code>verification_code<\/code> field.<\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nimport uuid\nfrom .database import Base\nfrom sqlalchemy import TIMESTAMP, Column, String, Boolean, text\nfrom sqlalchemy.dialects.postgresql import UUID\n\n\nclass User(Base):\n    __tablename__ = 'users'\n    id = Column(UUID(as_uuid=True), primary_key=True, nullable=False,\n                default=uuid.uuid4)\n    name = Column(String,  nullable=False)\n    email = Column(String, unique=True, nullable=False)\n    password = Column(String, nullable=False)\n    photo = Column(String, nullable=True)\n    verified = Column(Boolean, nullable=False, server_default='False')\n    verification_code = Column(String, nullable=True, unique=True)\n    role = Column(String, server_default='user', nullable=False)\n    created_at = Column(TIMESTAMP(timezone=True),\n                        nullable=False, server_default=text(\"now()\"))\n    updated_at = Column(TIMESTAMP(timezone=True),\n                        nullable=False, server_default=text(\"now()\"))\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the HTML Email Templates with Jinja2<\/h2>\n\n\n\n<p>There are numerous templating engines in Python, like:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/pythonhosted.org\/Cheetah\/\" target=\"_blank\" rel=\"noreferrer noopener\">Cheetah<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.fullstackpython.com\/mako.html\" target=\"_blank\" rel=\"noreferrer noopener\">Mako<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/chameleon.readthedocs.io\/en\/latest\/\" target=\"_blank\" rel=\"noreferrer noopener\">Chameleon<\/a><\/li>\n\n\n\n<li><a href=\"http:\/\/docs.diazo.org\/en\/latest\/\" target=\"_blank\" rel=\"noreferrer noopener\">Diazo<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/breily\/juno\" target=\"_blank\" rel=\"noreferrer noopener\">Juno<\/a>, etc<\/li>\n<\/ul>\n\n\n\n<p>However, we are going to use <a href=\"https:\/\/www.fullstackpython.com\/jinja2.html\" target=\"_blank\" rel=\"noreferrer noopener\">Jinja2<\/a> since it&#8217;s a popular Python templating engine written as a self-contained open source project.<\/p>\n\n\n\n<p>Run this command to install <a href=\"https:\/\/www.fullstackpython.com\/jinja2.html\" target=\"_blank\" rel=\"noreferrer noopener\">Jinja2<\/a><\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\npip install Jinja2\n<\/code>\n<\/pre>\n\n\n\n<p>After installing the <a href=\"https:\/\/www.fullstackpython.com\/jinja2.html\" target=\"_blank\" rel=\"noreferrer noopener\">Jinja2<\/a> package, create a <code>app\/templates<\/code> folder.<\/p>\n\n\n\n<p>Now let&#8217;s create a <code>app\/templates\/_styles.html<\/code> file to contain the CSS styles for the Email template. <\/p>\n\n\n\n<p><strong>app\/templates\/_styles.html<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-html\"><code>\n&lt;style&gt;\n  \/* -------------------------------------\n          GLOBAL RESETS\n      ------------------------------------- *\/\n\n  \/*All the styling goes here*\/\n\n  img {\n    border: none;\n    -ms-interpolation-mode: bicubic;\n    max-width: 100%;\n  }\n\n  body {\n    background-color: #f6f6f6;\n    font-family: sans-serif;\n    -webkit-font-smoothing: antialiased;\n    font-size: 14px;\n    line-height: 1.4;\n    margin: 0;\n    padding: 0;\n    -ms-text-size-adjust: 100%;\n    -webkit-text-size-adjust: 100%;\n  }\n\n  table {\n    border-collapse: separate;\n    mso-table-lspace: 0pt;\n    mso-table-rspace: 0pt;\n    width: 100%;\n  }\n  table td {\n    font-family: sans-serif;\n    font-size: 14px;\n    vertical-align: top;\n  }\n\n  \/* -------------------------------------\n          BODY &amp; CONTAINER\n      ------------------------------------- *\/\n\n  .body {\n    background-color: #f6f6f6;\n    width: 100%;\n  }\n\n  \/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something *\/\n  .container {\n    display: block;\n    margin: 0 auto !important;\n    \/* makes it centered *\/\n    max-width: 580px;\n    padding: 10px;\n    width: 580px;\n  }\n\n  \/* This should also be a block element, so that it will fill 100% of the .container *\/\n  .content {\n    box-sizing: border-box;\n    display: block;\n    margin: 0 auto;\n    max-width: 580px;\n    padding: 10px;\n  }\n\n  \/* -------------------------------------\n          HEADER, FOOTER, MAIN\n      ------------------------------------- *\/\n  .main {\n    background: #ffffff;\n    border-radius: 3px;\n    width: 100%;\n  }\n\n  .wrapper {\n    box-sizing: border-box;\n    padding: 20px;\n  }\n\n  .content-block {\n    padding-bottom: 10px;\n    padding-top: 10px;\n  }\n\n  .footer {\n    clear: both;\n    margin-top: 10px;\n    text-align: center;\n    width: 100%;\n  }\n  .footer td,\n  .footer p,\n  .footer span,\n  .footer a {\n    color: #999999;\n    font-size: 12px;\n    text-align: center;\n  }\n\n  \/* -------------------------------------\n          TYPOGRAPHY\n      ------------------------------------- *\/\n  h1,\n  h2,\n  h3,\n  h4 {\n    color: #000000;\n    font-family: sans-serif;\n    font-weight: 400;\n    line-height: 1.4;\n    margin: 0;\n    margin-bottom: 30px;\n  }\n\n  h1 {\n    font-size: 35px;\n    font-weight: 300;\n    text-align: center;\n    text-transform: capitalize;\n  }\n\n  p,\n  ul,\n  ol {\n    font-family: sans-serif;\n    font-size: 14px;\n    font-weight: normal;\n    margin: 0;\n    margin-bottom: 15px;\n  }\n  p li,\n  ul li,\n  ol li {\n    list-style-position: inside;\n    margin-left: 5px;\n  }\n\n  a {\n    color: #3498db;\n    text-decoration: underline;\n  }\n\n  \/* -------------------------------------\n          BUTTONS\n      ------------------------------------- *\/\n  .btn {\n    box-sizing: border-box;\n    width: 100%;\n  }\n  .btn &gt; tbody &gt; tr &gt; td {\n    padding-bottom: 15px;\n  }\n  .btn table {\n    width: auto;\n  }\n  .btn table td {\n    background-color: #ffffff;\n    border-radius: 5px;\n    text-align: center;\n  }\n  .btn a {\n    background-color: #ffffff;\n    border: solid 1px #3498db;\n    border-radius: 5px;\n    box-sizing: border-box;\n    color: #3498db;\n    cursor: pointer;\n    display: inline-block;\n    font-size: 14px;\n    font-weight: bold;\n    margin: 0;\n    padding: 12px 25px;\n    text-decoration: none;\n    text-transform: capitalize;\n  }\n\n  .btn-primary table td {\n    background-color: #3498db;\n  }\n\n  .btn-primary a {\n    background-color: #3498db;\n    border-color: #3498db;\n    color: #ffffff;\n  }\n\n  \/* -------------------------------------\n          OTHER STYLES THAT MIGHT BE USEFUL\n      ------------------------------------- *\/\n  .last {\n    margin-bottom: 0;\n  }\n\n  .first {\n    margin-top: 0;\n  }\n\n  .align-center {\n    text-align: center;\n  }\n\n  .align-right {\n    text-align: right;\n  }\n\n  .align-left {\n    text-align: left;\n  }\n\n  .clear {\n    clear: both;\n  }\n\n  .mt0 {\n    margin-top: 0;\n  }\n\n  .mb0 {\n    margin-bottom: 0;\n  }\n\n  .preheader {\n    color: transparent;\n    display: none;\n    height: 0;\n    max-height: 0;\n    max-width: 0;\n    opacity: 0;\n    overflow: hidden;\n    mso-hide: all;\n    visibility: hidden;\n    width: 0;\n  }\n\n  .powered-by a {\n    text-decoration: none;\n  }\n\n  hr {\n    border: 0;\n    border-bottom: 1px solid #f6f6f6;\n    margin: 20px 0;\n  }\n\n  \/* -------------------------------------\n          RESPONSIVE AND MOBILE FRIENDLY STYLES\n      ------------------------------------- *\/\n  @media only screen and (max-width: 620px) {\n    table.body h1 {\n      font-size: 28px !important;\n      margin-bottom: 10px !important;\n    }\n    table.body p,\n    table.body ul,\n    table.body ol,\n    table.body td,\n    table.body span,\n    table.body a {\n      font-size: 16px !important;\n    }\n    table.body .wrapper,\n    table.body .article {\n      padding: 10px !important;\n    }\n    table.body .content {\n      padding: 0 !important;\n    }\n    table.body .container {\n      padding: 0 !important;\n      width: 100% !important;\n    }\n    table.body .main {\n      border-left-width: 0 !important;\n      border-radius: 0 !important;\n      border-right-width: 0 !important;\n    }\n    table.body .btn table {\n      width: 100% !important;\n    }\n    table.body .btn a {\n      width: 100% !important;\n    }\n    table.body .img-responsive {\n      height: auto !important;\n      max-width: 100% !important;\n      width: auto !important;\n    }\n  }\n\n  \/* -------------------------------------\n          PRESERVE THESE STYLES IN THE HEAD\n      ------------------------------------- *\/\n  @media all {\n    .ExternalClass {\n      width: 100%;\n    }\n    .ExternalClass,\n    .ExternalClass p,\n    .ExternalClass span,\n    .ExternalClass font,\n    .ExternalClass td,\n    .ExternalClass div {\n      line-height: 100%;\n    }\n    .apple-link a {\n      color: inherit !important;\n      font-family: inherit !important;\n      font-size: inherit !important;\n      font-weight: inherit !important;\n      line-height: inherit !important;\n      text-decoration: none !important;\n    }\n    #MessageViewBody a {\n      color: inherit;\n      text-decoration: none;\n      font-size: inherit;\n      font-family: inherit;\n      font-weight: inherit;\n      line-height: inherit;\n    }\n    .btn-primary table td:hover {\n      background-color: #34495e !important;\n    }\n    .btn-primary a:hover {\n      background-color: #34495e !important;\n      border-color: #34495e !important;\n    }\n  }\n&lt;\/style&gt;\n<\/code>\n<\/pre>\n\n\n\n<p>Next, let&#8217;s create the <code>app\/templates\/base.html<\/code> file that we will extend to generate any kind of HTML template like:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Email verification HTML template<\/li>\n\n\n\n<li>Password reset HTML template<\/li>\n\n\n\n<li>Welcome Email HTML template<\/li>\n<\/ul>\n\n\n\n<p>Jinja2 allows us to use variables in the template by passing a defined context dictionary to the template.<\/p>\n\n\n\n<p>To include the <code>_styles.html<\/code> file in the <code>base.html<\/code> template, we use the <code>include<\/code>&nbsp;tag provided by <a href=\"https:\/\/www.fullstackpython.com\/jinja2.html\" target=\"_blank\" rel=\"noreferrer noopener\">Jinja2<\/a> to render the <code>_styles.html<\/code> template and output the result into the <code>base.html<\/code> template.<\/p>\n\n\n\n<p>Also, we use&nbsp;<code>{%&nbsp;block&nbsp;%}<\/code> tags to define a simple HTML skeleton that will be overridden by a child template.<\/p>\n\n\n\n<p><strong>app\/templates\/base.html<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-html\"><code>\n&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n  &lt;head&gt;\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/&gt;\n    &lt;meta http-equiv=\"Content-Type\" content=\"text\/html; charset=UTF-8\" \/&gt;\n    &lt;title&gt;{{subject}}&lt;\/title&gt;\n    {% include '_styles.html' %}\n  &lt;\/head&gt;\n  &lt;body&gt;\n    &lt;table\n      role=\"presentation\"\n      border=\"0\"\n      cellpadding=\"0\"\n      cellspacing=\"0\"\n      class=\"body\"\n    &gt;\n      &lt;tr&gt;\n        &lt;td&gt;&amp;nbsp;&lt;\/td&gt;\n        &lt;td class=\"container\"&gt;\n          &lt;div class=\"content\"&gt;\n            &lt;!-- START CENTERED WHITE CONTAINER --&gt;\n            &lt;table role=\"presentation\" class=\"main\"&gt;\n              &lt;!-- START MAIN CONTENT AREA --&gt;\n              &lt;tr&gt;\n                &lt;td class=\"wrapper\"&gt;\n                  &lt;table\n                    role=\"presentation\"\n                    border=\"0\"\n                    cellpadding=\"0\"\n                    cellspacing=\"0\"\n                  &gt;\n                    &lt;tr&gt;\n                      &lt;td&gt;{% block content %} {% endblock %}&lt;\/td&gt;\n                    &lt;\/tr&gt;\n                  &lt;\/table&gt;\n                &lt;\/td&gt;\n              &lt;\/tr&gt;\n\n              &lt;!-- END MAIN CONTENT AREA --&gt;\n            &lt;\/table&gt;\n            &lt;!-- END CENTERED WHITE CONTAINER --&gt;\n          &lt;\/div&gt;\n        &lt;\/td&gt;\n        &lt;td&gt;&amp;nbsp;&lt;\/td&gt;\n      &lt;\/tr&gt;\n    &lt;\/table&gt;\n  &lt;\/body&gt;\n&lt;\/html&gt;\n<\/code>\n<\/pre>\n\n\n\n<p>Finally, we use the <code>{%&nbsp;extends&nbsp;%}<\/code>&nbsp;tag to tell the template engine that the  <code>verification.html<\/code> template extends the <code>base.html<\/code> template.<\/p>\n\n\n\n<p>This ensures that the content between the <code>{% block content %}...{% endblock %}<\/code> overrides the content block in the base template.<\/p>\n\n\n\n<p><strong>app\/templates\/verification.html<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-html\"><code>\n{% extends 'base.html' %} {% block content %}\n\n&lt;p&gt;Hi {{first_name}},&lt;\/p&gt;\n&lt;p&gt;\n  Thanks for creating an account with us. Please verify your email address by\n  clicking the button below.\n&lt;\/p&gt;\n&lt;table\n  role=\"presentation\"\n  border=\"0\"\n  cellpadding=\"0\"\n  cellspacing=\"0\"\n  class=\"btn btn-primary\"\n&gt;\n  &lt;tbody&gt;\n    &lt;tr&gt;\n      &lt;td align=\"left\"&gt;\n        &lt;table role=\"presentation\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"&gt;\n          &lt;tbody&gt;\n            &lt;tr&gt;\n              &lt;td&gt;\n                &lt;a href=\"{{url}}\" target=\"_blank\"&gt;Verify email address&lt;\/a&gt;\n              &lt;\/td&gt;\n            &lt;\/tr&gt;\n          &lt;\/tbody&gt;\n        &lt;\/table&gt;\n      &lt;\/td&gt;\n    &lt;\/tr&gt;\n  &lt;\/tbody&gt;\n&lt;\/table&gt;\n&lt;p&gt;Good luck! Hope it works.&lt;\/p&gt;\n\n{% endblock %}\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Set up SMTP Email Sender<\/h2>\n\n\n\n<p>Python comes with a built-in <code>smtplib<\/code> module for sending emails, however, we gonna use the <a href=\"https:\/\/sabuhish.github.io\/fastapi-mail\/\" target=\"_blank\" rel=\"noreferrer noopener\">fastapi-mail<\/a> package since it works <strong>out-of-the-box<\/strong> with <a href=\"https:\/\/www.fullstackpython.com\/jinja2.html\" target=\"_blank\" rel=\"noreferrer noopener\">Jinja2<\/a>.<\/p>\n\n\n\n<p>Run this command to install the <a href=\"https:\/\/sabuhish.github.io\/fastapi-mail\/\" target=\"_blank\" rel=\"noreferrer noopener\">fastapi-mail<\/a> package:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\n pip install fastapi-mail\n<\/code>\n<\/pre>\n\n\n\n<p>After the installation is complete, create a <code>app\/email.py<\/code> file and add the following imports:<\/p>\n\n\n\n<p><strong>app\/email.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom typing import List\nfrom fastapi_mail import FastMail, MessageSchema, ConnectionConfig\nfrom pydantic import EmailStr, BaseModel\nfrom . import models\nfrom .config import settings\nfrom jinja2 import Environment, select_autoescape, PackageLoader\n<\/code>\n<\/pre>\n\n\n\n<p>To load the HTML templates from the templates folder, we will use the<code>PackageLoader<\/code> method provided by <a href=\"https:\/\/www.fullstackpython.com\/jinja2.html\" target=\"_blank\" rel=\"noreferrer noopener\">Jinja2<\/a>.<\/p>\n\n\n\n<p>With the <code>PackageLoader<\/code> method, you need to make sure the templates folder is in a Python package.<\/p>\n\n\n\n<p>You can easily turn the folder containing the templates folder into a Python package by creating an empty <code>__init__.py<\/code> file in it.<\/p>\n\n\n\n<p>Alternatively, you can use the <code>FileSystemLoader()<\/code> method provided by <a href=\"https:\/\/www.fullstackpython.com\/jinja2.html\" target=\"_blank\" rel=\"noreferrer noopener\">Jinja2<\/a> to load the templates from a directory in the file system.<\/p>\n\n\n\n<p><strong>app\/email.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\n# imports (..)\n\nenv = Environment(\n    loader=PackageLoader('app', 'templates'),\n    autoescape=select_autoescape(['html', 'xml'])\n)\n<\/code>\n<\/pre>\n\n\n\n<p>With that out of the way, let&#8217;s create a utility class that we can use to send multiple emails at once by simply appending the methods.<\/p>\n\n\n\n<p><strong>app\/email.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\nfrom typing import List\nfrom fastapi_mail import FastMail, MessageSchema, ConnectionConfig\nfrom pydantic import EmailStr, BaseModel\nfrom . import models\nfrom .config import settings\nfrom jinja2 import Environment, select_autoescape, PackageLoader\n\n\nenv = Environment(\n    loader=PackageLoader('app', 'templates'),\n    autoescape=select_autoescape(['html', 'xml'])\n)\n\n\nclass EmailSchema(BaseModel):\n    email: List[EmailStr]\n\n\nclass Email:\n    def __init__(self, user: models.User, url: str, email: List[EmailStr]):\n        self.name = user.name\n        self.sender = 'Codevo &lt;admin@admin.com&gt;'\n        self.email = email\n        self.url = url\n        pass\n\n    async def sendMail(self, subject, template):\n        # Define the config\n        conf = ConnectionConfig(\n            MAIL_USERNAME=settings.EMAIL_USERNAME,\n            MAIL_PASSWORD=settings.EMAIL_PASSWORD,\n            MAIL_FROM=settings.EMAIL_FROM,\n            MAIL_PORT=settings.EMAIL_PORT,\n            MAIL_SERVER=settings.EMAIL_HOST,\n            MAIL_STARTTLS=False,\n            MAIL_SSL_TLS=False,\n            USE_CREDENTIALS=True,\n            VALIDATE_CERTS=True\n        )\n        # Generate the HTML template base on the template name\n        template = env.get_template(f'{template}.html')\n\n        html = template.render(\n            url=self.url,\n            first_name=self.name,\n            subject=subject\n        )\n\n        # Define the message options\n        message = MessageSchema(\n            subject=subject,\n            recipients=self.email,\n            body=html,\n            subtype=\"html\"\n        )\n\n        # Send the email\n        fm = FastMail(conf)\n        await fm.send_message(message)\n\n    async def sendVerificationCode(self):\n        await self.sendMail('Your verification code (Valid for 10min)', 'verification')\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">How to Send the HTML Email<\/h2>\n\n\n\n<p>First and foremost, we need to generate a random 10-byte string and hash it using the <code>sha256<\/code> algorithm. Feel free to change the size of the random byte.<\/p>\n\n\n\n<p>We then need to save the hashed string in the database and send the unhashed string to the user&#8217;s email.<\/p>\n\n\n\n<p>To send the email, we first construct the URL and call the <code>sendVerificationCode()<\/code> method we defined in the <code>Email<\/code> class.<\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\n try:\n        # Send Verification Email\n        token = randbytes(10)\n        hashedCode = hashlib.sha256()\n        hashedCode.update(token)\n        verification_code = hashedCode.hexdigest()\n        user_query.update(\n            {'verification_code': verification_code}, synchronize_session=False)\n        db.commit()\n        url = f\"{request.url.scheme}:\/\/{request.client.host}:{request.url.port}\/api\/auth\/verifyemail\/{token.hex()}\"\n        await Email(new_user, url, [payload.email]).sendVerificationCode()\n    except Exception as error:\n        print('Error', error)\n        user_query.update(\n            {'verification_code': None}, synchronize_session=False)\n        db.commit()\n        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,\n                            detail='There was an error sending email')\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Update the SignUp Path Operation Function<\/h2>\n\n\n\n<p>Now edit the <code>create_user()<\/code> operation function to send the verification email after the user has been successfully added to the database. <\/p>\n\n\n\n<p><strong>app\/routers\/auth.py<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\n@router.post('\/register', status_code=status.HTTP_201_CREATED)\nasync def create_user(payload: schemas.CreateUserSchema, request: Request, db: Session = Depends(get_db)):\n    # Check if user already exist\n    user_query = db.query(models.User).filter(\n        models.User.email == EmailStr(payload.email.lower()))\n    user = user_query.first()\n    if user:\n        raise HTTPException(status_code=status.HTTP_409_CONFLICT,\n                            detail='Account already exist')\n    # Compare password and passwordConfirm\n    if payload.password != payload.passwordConfirm:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST, detail='Passwords do not match')\n    #  Hash the password\n    payload.password = utils.hash_password(payload.password)\n    del payload.passwordConfirm\n    payload.role = 'user'\n    payload.verified = False\n    payload.email = EmailStr(payload.email.lower())\n    new_user = models.User(**payload.dict())\n    db.add(new_user)\n    db.commit()\n    db.refresh(new_user)\n\n    try:\n        # Send Verification Email\n        token = randbytes(10)\n        hashedCode = hashlib.sha256()\n        hashedCode.update(token)\n        verification_code = hashedCode.hexdigest()\n        user_query.update(\n            {'verification_code': verification_code}, synchronize_session=False)\n        db.commit()\n        url = f\"{request.url.scheme}:\/\/{request.client.host}:{request.url.port}\/api\/auth\/verifyemail\/{token.hex()}\"\n        await Email(new_user, url, [payload.email]).sendVerificationCode()\n    except Exception as error:\n        print('Error', error)\n        user_query.update(\n            {'verification_code': None}, synchronize_session=False)\n        db.commit()\n        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,\n                            detail='There was an error sending email')\n    return {'status': 'success', 'message': 'Verification token successfully sent to your email'}\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create a Controller to Verify the Code<\/h2>\n\n\n\n<p>Finally, let&#8217;s create a path operation function to validate the email verification code and update the user&#8217;s information assuming the code is valid.<\/p>\n\n\n\n<pre class=\"line-numbers language-py\"><code>\n@router.get('\/verifyemail\/{token}')\ndef verify_me(token: str, db: Session = Depends(get_db)):\n    hashedCode = hashlib.sha256()\n    hashedCode.update(bytes.fromhex(token))\n    verification_code = hashedCode.hexdigest()\n    user_query = db.query(models.User).filter(\n        models.User.verification_code == verification_code)\n    db.commit()\n    user = user_query.first()\n   if not user:\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN, detail=\"Invalid code or user doesn't exist\")\n    if user.verified:\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN, detail='Email can only be verified once')\n    user_query.update(\n        {'verified': True, 'verification_code': None}, synchronize_session=False)\n    db.commit()\n    return {\n        \"status\": \"success\",\n        \"message\": \"Account verified successfully\"\n    }\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Congrats on reaching the end. In this article, you learned how to send HTML emails in FastAPI and Python using <a href=\"https:\/\/www.fullstackpython.com\/jinja2.html\" target=\"_blank\" rel=\"noreferrer noopener\">Jinja2<\/a> and <a href=\"https:\/\/sabuhish.github.io\/fastapi-mail\/\" target=\"_blank\" rel=\"noreferrer noopener\">FastApi-MAIL<\/a> packages.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Send HTML Emails with FastAPI Source Code<\/h2>\n\n\n\n<p>Check out the source codes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/wpcodevo\/python_fastapi\/tree\/python_fastapi_verify_email\" target=\"_blank\" rel=\"noreferrer noopener\">Python FastAPI Verify Email Source Code<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/wpcodevo\/react-mui-frontend.git\" target=\"_blank\" rel=\"noreferrer noopener\">React frontend app<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>In this article, you&#8217;ll learn how to send HTML emails with Python, FastAPI, PostgreSQL, Jinja2, and Docker-compose. Also, you&#8217;ll learn how to dynamically generate HTML&#8230;<\/p>\n","protected":false},"author":1,"featured_media":4881,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[20],"tags":[25],"class_list":["post-4840","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-python","tag-python"],"acf":[],"_links":{"self":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4840","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/comments?post=4840"}],"version-history":[{"count":2,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4840\/revisions"}],"predecessor-version":[{"id":11269,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4840\/revisions\/11269"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media\/4881"}],"wp:attachment":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media?parent=4840"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/categories?post=4840"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/tags?post=4840"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}