diff --git a/snooty.toml b/snooty.toml
index 8b95c797..25f3985e 100644
--- a/snooty.toml
+++ b/snooty.toml
@@ -11,7 +11,8 @@ toc_landing_pages = [
"/crud/query",
"/crud/update",
"/monitoring-and-logging",
- "/reference"
+ "/reference",
+ "/integrations"
]
intersphinx = [
diff --git a/source/integrations.txt b/source/integrations.txt
index 16a337e4..05cc29d0 100644
--- a/source/integrations.txt
+++ b/source/integrations.txt
@@ -18,6 +18,10 @@ Third-Party Integrations and Tools
.. meta::
:keywords: pypi, package, web, module, pip
+.. toctree::
+
+ Tutorial: Flask and Celery Integration
+
Overview
--------
diff --git a/source/integrations/flask-celery-integration.txt b/source/integrations/flask-celery-integration.txt
new file mode 100644
index 00000000..c25a3826
--- /dev/null
+++ b/source/integrations/flask-celery-integration.txt
@@ -0,0 +1,694 @@
+.. _pymongo-flask-celery:
+.. original URL: https://www.mongodb.com/developer/products/mongodb/python-flask-celery-newsletter/
+
+======================================
+Tutorial: Flask and Celery Integration
+======================================
+
+.. contents:: On this page
+ :local:
+ :backlinks: none
+ :depth: 2
+ :class: singlecol
+
+.. facet::
+ :name: genre
+ :values: tutorial
+
+.. meta::
+ :keywords: code example, batch, framework
+
+Overview
+--------
+
+In this tutorial, you can learn how to use MongoDB, Flask, and Celery to build a
+newsletter platform. This application allows users to subscribe to newsletters,
+and administrators to manage and send batch emails asynchronously.
+
+Flask
+~~~~~
+
+Flask is a lightweight web application framework with built-in configuration and
+convention defaults that provide consistency to developers across projects. For
+more information, see the `Flask webpage
+`__.
+
+Celery
+~~~~~~
+
+Celery is an open-source distributed task queue that handles large volumes of
+messages efficiently. It supports asynchronous processing and task scheduling.
+For more information, see the `Celery webpage
+`__.
+
+Tutorial
+--------
+
+This tutorial recreates the sample application in the :github:`Newsletter
+Platform with JavaScript, Flask, and MongoDB sample project
+` GitHub repository.
+
+Prerequisites
+~~~~~~~~~~~~~
+
+Ensure that you have the following components installed and set up before you
+start this tutorial:
+
+- A MongoDB cluster. We recommend that you use Atlas. To learn how
+ to create an Atlas cluster, see the
+ :atlas:`Get Started with Atlas ` page
+ in the Atlas documentation.
+- A database named ``newsletter`` in your cluster. For more information, see
+ the :atlas:`Create a Database ` page
+ in the Atlas guide.
+- `RabbitMQ `__ to use as a message
+ broker for Celery.
+- `Gmail `__ to use as an SMTP server. For more information about
+ SMTP servers, see the :wikipedia:`Simple Mail Transfer Protocol
+ ` Wikipedia page.
+- `Python 3.9 or later `__
+
+Setup
+~~~~~
+
+.. procedure::
+ :style: connected
+
+ .. step:: Create your project directory and structure
+
+ The name of your project directory is ``newsletter``. Create your
+ directory and navigate to it by running the following commands in
+ terminal:
+
+ .. code-block:: bash
+
+ mkdir newsletter
+ cd newsletter
+
+ The following files will hold the code for your application:
+
+ - ``app.py``: The main entry point for your Flask application
+ - ``config.py``: Configuration settings for your application, including
+ the MongoDB connection URI, mail server configuration, Celery broker
+ connection, and any other environment-specific variables
+ - ``tasks.py``: Defines background tasks to send emails asynchronously
+ - ``routes.py``: Defines the routes (URLs) that your application responds
+ to
+
+ We recommend structuring your application to separate concerns, which can
+ make the application modular and more maintainable.
+
+ In your project directory, create the following structure:
+
+ .. code-block:: none
+
+ newsletter/
+ ├── app.py
+ ├── config.py
+ ├── routes.py
+ ├── tasks.py
+ ├── templates/
+ │ ├── admin.html
+ │ └── subscribe.html
+ └── static/
+ └── styles.css
+
+ .. step:: Install the required Python packages
+
+ Your application uses the following libraries:
+
+ - `Flask `__ for handling
+ the web server and routing
+ - `Flask-Mail `__ for sending emails
+ from your application
+ - :ref:`{+driver-short+} `
+ - `Celery `__ to manage tasks, such
+ as sending batch emails
+
+ .. tip:: Use a Virtual Environment
+
+ Python `virtual environments
+ `__ allow you to install
+ different versions of libraries for different projects. Before running
+ any ``pip`` commands, ensure that your ``virtualenv`` is active.
+
+ Run the following ``pip`` command in your terminal to install the
+ dependencies:
+
+ .. code-block:: bash
+
+ pip install flask-pymongo Flask-Mail celery
+
+Configure Your Application
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``config.py`` file contains settings and credentials to perform the
+following actions:
+
+- Connect Celery to RabbitMQ as its message broker
+- Configure Flask-Mail to use Gmail as its SMTP server
+- Connect your application to your MongoDB deployment
+
+Define the necessary configurations by adding the following code to your
+``config.py`` file:
+
+.. code-block:: python
+
+ import os
+
+ class Config:
+ MAIL_USERNAME = '' # Your email address without '@gmail.com'
+ MAIL_PASSWORD = ''
+ MAIL_DEFAULT_SENDER = ''
+ MONGO_URI = ''
+ DATABASE_NAME = "newsletter"
+ ALLOWED_IPS = ['127.0.0.1']
+ MAIL_SERVER = 'smtp.gmail.com'
+ MAIL_PORT = 587
+ MAIL_USE_TLS = True
+ CELERY_BROKER_URL = 'amqp://guest:guest@localhost//'
+ RESULT_BACKEND = MONGO_URI + '/celery_results'
+
+You must provide your Gmail credentials and email (``MAIL_USERNAME``,
+``MAIL_PASSWORD``, and ``MAIL_DEFAULT_SENDER``) to enable your application to
+send emails. For security purposes, we recommend that you generate an app
+password to use, rather than using your primary password. For more information,
+see the `App Password settings `__ in
+your Google Account.
+
+You must also provide a connection string to set as the ``MONGO_URI``
+environment variable. For more information, see the :ref:`Create a Connection
+String ` section of this guide.
+
+The provided Celery broker URL (``CELERY_BROKER_URL``) specifies RabbitMQ as its
+broker, but you can customize this URL to support other implementations. For
+more information, see the `Broker Settings
+`__
+section of the Celery documentation.
+
+The ``ALLOWED_IPS`` list is used to control access to the :guilabel:`Send
+Newsletter` page. The rest of the variables configure the Flask and Celery
+components.
+
+Initialize Flask, MongoDB, and Celery
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``app.py`` file initializes and configures the core components of your
+application. It performs the following tasks:
+
+- Creates a Flask application and loads configuration constants
+- Initializes a Flask-Mail instance with the app's mail server settings
+- Connects to the ``newsletter`` MongoDB database by using the {+driver-short+}
+ driver
+- Creates a Celery instance configured with the Flask app and your chosen broker
+
+Initialize Flask, MongoDB, and Celery by adding the following code to your
+``app.py`` file:
+
+.. code-block:: python
+
+ from flask import Flask
+ from flask_mail import Mail
+ from flask_pymongo import PyMongo
+ from celery import Celery
+
+ # Create a Flask application
+ app = Flask(__name__)
+ app.config.from_object('config.Config')
+
+ # Create a Flask-Mail instance
+ mail = Mail(app)
+
+ # Connect to MongoDB
+ client = PyMongo(app).cx
+ db = client[app.config['DATABASE_NAME']]
+
+ # Create a Celery instance
+ celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
+ celery.conf.update(app.config)
+
+ from routes import *
+ from tasks import *
+
+ if __name__ == '__main__':
+ app.run(debug=True)
+
+Create a Celery Task
+~~~~~~~~~~~~~~~~~~~~
+
+The Celery task uses the components instantiated in your ``app.py`` file to send
+a newsletter email to subscribers.
+
+The ``@celery.task()`` decorator registers the function as a Celery task.
+Setting ``bind=True`` means the function receives the task instance as the
+``self`` argument, which allows it to access Celery task methods and metadata.
+For more information about tasks, see the `celery.app.task
+`__ API
+documentation.
+
+Since this task runs outside of Flask's HTTP request cycle, you must manually
+provide application context by wrapping the email logic in a ``with
+app.app_context()`` block. This gives Flask access to other components like the
+Flask-Mail ``mail`` instance and the {+driver-short+} connection to your
+``newsletter`` MongoDB database.
+
+This function loops through the list of ``subscribers``, creates an email using
+the Flask-Mail ``Message`` class, and then sends it to each user by using the
+``mail`` object. After each email is sent, it logs the delivery by inserting a
+document into your MongoDB ``deliveries`` collection to record that the message
+was sent. Each email operation is wrapped in a ``try`` block to ensure that, in
+the case of an error, the failure is logged and the database is not updated with
+a false delivery record.
+
+Define your ``send_emails()`` function by adding the following code to your
+``tasks.py`` file:
+
+.. code-block:: python
+
+ from flask_mail import Message
+ from app import app, mail, db, celery
+ from datetime import datetime
+
+ @celery.task(bind=True)
+ def send_emails(self, subscribers, title, body):
+ with app.app_context():
+ for subscriber in subscribers:
+ try:
+ print(f"Sending email to {subscriber['email']}")
+ msg = Message(title, recipients=[subscriber['email']])
+ msg.body = body
+ mail.send(msg)
+ db.deliveries.insert_one({
+ 'email': subscriber['email'],
+ 'title': title,
+ 'body': body,
+ 'delivered_at': datetime.utcnow()
+ })
+ print("Email sent")
+
+ except Exception as e:
+ print(f"Failed to send email to {subscriber['email']}: {str(e)}")
+
+ return {'result': 'All emails sent'}
+
+
+Define Your Routes
+~~~~~~~~~~~~~~~~~~
+
+In Flask, the ``@app.route()`` decorator assigns a URL path to a specific
+function. In the following code, it is used to define the root (``/``),
+``/admin``, ``/subscribe``, and ``/send-newsletters`` routes. The optional
+``methods`` parameter is used in some instances to define a list of allowable
+HTTP methods.
+
+The ``@app.before_request()`` decorator sets a function to run before every
+request. In this case, the function provides some basic security by limiting
+access to the ``admin`` page to IP addresses listed in the ``ALLOWED_IPS``
+parameter defined in the ``config.py`` file. Specifically, access is only
+allowed for the ``localhost``.
+
+The root and ``/admin`` routes render pages using the ``render_template()``
+method. The ``/subscribe`` and ``/send-newsletters`` routes access request
+parameters in ``request.form[]`` to execute commands, and then return HTTP
+responses.
+
+Define your routes by adding the following code to your ``routes.py`` file:
+
+.. code-block:: python
+
+ from flask import render_template, request, abort, jsonify
+ from app import app, db
+ from tasks import send_emails
+
+ @app.before_request
+ def limit_remote_addr():
+ if 'X-Forwarded-For' in request.headers:
+ remote_addr = request.headers['X-Forwarded-For'].split(',')[0]
+ else:
+ remote_addr = request.remote_addr
+
+ if request.endpoint == 'admin' and remote_addr not in app.config['ALLOWED_IPS']:
+ abort(403)
+
+ @app.route('/')
+ def home():
+ return render_template('subscribe.html')
+
+ @app.route('/admin')
+ def admin():
+ return render_template('admin.html')
+
+ @app.route('/subscribe', methods=['POST'])
+ def subscribe():
+ first_name = request.form['firstname']
+ last_name = request.form['lastname']
+ email = request.form['email']
+
+ if db.users.find_one({'email': email}):
+ return """
+
+ """, 200
+
+ @app.route('/send-newsletters', methods=['POST'])
+ def send_newsletters():
+ title = request.form['title']
+ body = request.form['body']
+ subscribers = list(db.users.find({'subscribed': True}))
+
+ for subscriber in subscribers:
+ subscriber['_id'] = str(subscriber['_id'])
+
+ send_emails.apply_async(args=[subscribers, title, body])
+ return jsonify({'message': 'Emails are being sent!'}), 202
+
+You can add more security protections or customize user-facing alerts for your
+application in this file.
+
+Create Your Page Templates
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The HTML files in the ``templates`` directory define the user interface, and are
+written using standard HTML. Because this application uses asynchronous HTTP
+requests, the scripts in these files use `Fetch API calls
+`__. These scripts
+also handle timeouts and errors.
+
+Subscribe Page
+```````````````
+
+Copy the following code into your ``subscribe.html`` file to create your
+:guilabel:`Subscribe to Newsletter` page.
+
+.. code-block:: html
+
+
+
+
+
+
+ Subscribe to Newsletter
+
+
+
+
Subscribe to our Newsletter
+
+
+
+
+
+
+Admin Page
+```````````
+
+The admin page script displays an alert to the user that indicates the
+success of the ``send_newsletter`` call.
+
+Copy the following code into your ``admin.html`` file to create your
+:guilabel:`Send Newsletter` page:
+
+.. code-block:: html
+
+
+
+
+
+
+ Admin - Send Newsletter
+
+
+
+
Send Newsletter
+
+
+
+
+
+
+Format Your Pages
+~~~~~~~~~~~~~~~~~
+
+You can apply a style sheet to your templates by adding the following code to
+the ``styles.css`` file:
+
+.. code-block:: css
+
+ body {
+ font-family: system-ui;
+ font-optical-sizing: auto;
+ font-weight: 300;
+ font-style: normal;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+ background-color: #040100;
+ }
+
+ h1 {
+ color: white;
+ }
+
+ form {
+ background: #023430;
+ padding: 30px 40px;
+ border-radius: 8px;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+ width: 100%;
+ max-width: 400px;
+ margin: 20px 0;
+ }
+
+ label {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: bold;
+ color: white;
+ }
+
+ input[type="text"],
+ input[type="email"],
+ textarea {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ font-size: 16px;
+ }
+
+ button {
+ background: #00ED64;
+ color: white;
+ padding: 10px 20px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 16px;
+ font-family: "Nunito", sans-serif;
+ }
+
+ button:hover {
+ background: #00684A;
+ }
+
+ #response {
+ margin-top: 20px;
+ font-size: 16px;
+ color: #28a745;
+ }
+
+ footer {
+ text-align: center;
+ padding: 20px;
+ margin-top: 20px;
+ font-size: 16px;
+ color: #666;
+ }
+
+Test the Application
+~~~~~~~~~~~~~~~~~~~~
+
+After you complete the previous steps, you have a working application that uses
+MongoDB, Flask, and Celery to manage a newsletter platform.
+
+You can use the following steps to test your application:
+
+.. procedure::
+ :style: connected
+
+ .. step:: Start your background services
+
+ Start your RabbitMQ node. For instructions, see the `RabbitMQ
+ documentation `__ for your
+ operating system.
+
+ On MacOS:
+
+ .. code-block:: bash
+
+ brew services start rabbitmq
+
+ On Windows:
+
+ .. code-block:: bash
+
+ rabbitmq-service start
+
+ On Linux/Unix:
+
+ .. code-block:: bash
+
+ sudo systemctl start rabbitmq-server
+
+ .. step:: Start your application
+
+ Use the following code to start your application:
+
+ .. code-block:: bash
+
+ flask --app app run
+
+ In another terminal, start the Celery worker:
+
+ .. code-block:: bash
+
+ celery -A app.celery worker --loglevel=info
+
+ .. step:: Create a subscriber
+
+ Navigate to ``__ in your browser to open the
+ :guilabel:`Subscribe to our Newsletter` page.
+
+ Enter the subscriber information and click :guilabel:`Subscribe`.
+
+ To confirm that you created a new subscriber, open `Atlas
+ `__ and navigate to the
+ ``users`` collection in your ``newsletter`` database.
+
+ .. step:: Dispatch a newsletter
+
+ Navigate to ``__ in your browser to open the
+ :guilabel:`Send Newsletter` page. Enter the newsletter details and click
+ :guilabel:`Send`.
+
+ Your Celery worker log will display an ``Email sent`` log entry similar to
+ the following image:
+
+ .. code-block:: bash
+
+ [2025-05-27 09:54:43,873: INFO/ForkPoolWorker-7] Task tasks.send_emails[7d7f9616-7b9b-4508-a889-95c35f54fe43] succeeded in 3.93334774998948s: {'result': 'All emails sent'}
+ [2025-05-27 10:04:52,043: INFO/MainProcess] Task tasks.send_emails[ac2ec70f-2d3e-444a-95bb-185ac659f460] received
+ [2025-05-27 10:04:52,046: WARNING/ForkPoolWorker-7] Sending email to
+ [2025-05-27 10:04:53,474: WARNING/ForkPoolWorker-7] Email sent
+
+ You can also confirm that you sent an email by navigating to the
+ ``deliveries`` collection in your ``newsletter`` database.
+
+Next Steps
+~~~~~~~~~~
+
+This application demonstrates how to integrate a Flask application with the
+Celery task queue to manage subscriber data and send batch emails. You can
+build on this application to experiment with Flask or Celery. Some possible
+improvements include the following changes:
+
+- Add `retries `__ to your ``send_emails`` function
+- `Format your newsletter `__
+- Implement more rigorous `security features `__
+
+More Resources
+--------------
+
+For more information about the components used in this tutorial, see the
+following resources:
+
+- `Flask `__
+- `Flask-Mail `__
+- `Celery `__
+- `RabbitMQ `__
+
+To find support or to contribute to the MongoDB community, see the `MongoDB
+Developer Community `__ page.
\ No newline at end of file