Installing Mail module borked my site

This has been a struggle for a week.

I install Mail, run the requirements and every single time despite success in the migration, it tells me Module Mail not found.

My plan? To install Mail on my current site called “frontend”. All of this is running on a custom Docker build.

This is the error I get when I try restart Docker

backend-1      | Traceback (most recent call last):
backend-1      |   File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 75, in get_app_commands
backend-1      |     app_command_module = importlib.import_module(f"{app}.commands")
backend-1      |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1      |   File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
backend-1      |     return _bootstrap._gcd_import(name[level:], package, level)
backend-1      |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1      |   File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
backend-1      |   File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
backend-1      |   File "<frozen importlib._bootstrap>", line 1126, in _find_and_load_unlocked
backend-1      |   File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
backend-1      |   File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
backend-1      |   File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
backend-1      |   File "<frozen importlib._bootstrap>", line 1140, in _find_and_load_unlocked
backend-1      | ModuleNotFoundError: No module named 'mail'
backend-1      | Traceback (most recent call last):
backend-1      |   File "<frozen runpy>", line 198, in _run_module_as_main
backend-1      |   File "<frozen runpy>", line 88, in _run_code
backend-1      |   File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 114, in <module>
backend-1      |     main()
backend-1      |   File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 20, in main
backend-1      |     click.Group(commands=commands)(prog_name="bench")
backend-1      |   File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/click/core.py", line 1442, in __call__
backend-1      |     return self.main(*args, **kwargs)
backend-1      |            ^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1      |   File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/click/core.py", line 1363, in main
backend-1      |     rv = self.invoke(ctx)
backend-1      |          ^^^^^^^^^^^^^^^^
backend-1      |   File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/click/core.py", line 1830, in invoke
backend-1      |     return _process_result(sub_ctx.command.invoke(sub_ctx))
backend-1      |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1      |   File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/click/core.py", line 1827, in invoke
backend-1      |     super().invoke(ctx)
backend-1      |   File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/click/core.py", line 1226, in invoke
backend-1      |     return ctx.invoke(self.callback, **ctx.params)
backend-1      |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1      |   File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/click/core.py", line 794, in invoke
backend-1      |     return callback(*args, **kwargs)
backend-1      |            ^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1      |   File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/click/decorators.py", line 34, in new_func
backend-1      |     return f(get_current_context(), *args, **kwargs)
backend-1      |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1      |   File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 44, in app_group
backend-1      |     ctx.obj = {"sites": get_sites(site), "force": force, "verbose": verbose, "profile": profile}
backend-1      |                         ^^^^^^^^^^^^^^^
backend-1      |   File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 56, in get_sites
backend-1      |     elif default_site := frappe.get_conf().default_site:
backend-1      |                          ^^^^^^^^^^^^^^^^^
backend-1      |   File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 406, in get_conf
backend-1      |     with init_site(site):
backend-1      |   File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 416, in __enter__
backend-1      |     init(self.site)
backend-1      |   File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 258, in init
backend-1      |     setup_module_map(include_all_apps=not (frappe.request or frappe.job or frappe.flags.in_migrate))
backend-1      |   File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1670, in setup_module_map
backend-1      |     for module in get_module_list(app):
backend-1      |                   ^^^^^^^^^^^^^^^^^^^^
backend-1      |   File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1518, in get_module_list
backend-1      |     return get_file_items(get_app_path(app_name, "modules.txt"))
backend-1      |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1      |   File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1483, in get_app_path
backend-1      |     return get_pymodule_path(app_name, *joins)
backend-1      |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1      |   File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1513, in get_pymodule_path
backend-1      |     return abspath(join(dirname(get_module(scrub(modulename)).__file__ or ""), *joins))
backend-1      |                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1      |   File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1454, in get_module
backend-1      |     return importlib.import_module(modulename)
backend-1      |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1      |   File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
backend-1      |     return _bootstrap._gcd_import(name[level:], package, level)
backend-1      |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
backend-1      |   File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
backend-1      |   File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
backend-1      |   File "<frozen importlib._bootstrap>", line 1140, in _find_and_load_unlocked
backend-1      | ModuleNotFoundError: No module named 'mail'
backend-1 exited with code 1

Worse, I have tried uninstalling the app, but even getting the backend image up is impossible unless I remove mail fro apps.txt. I have gone to the extend of messing with apps.json and that did little and worse, I set it to apps.json.bak cause Docker was not running with it in the state it was. Bad things happened there.

When I finally get the thing to run, all the pages bring this back

ModuleNotFoundError

ModuleNotFoundError: No module named 'mail'
Traceback (most recent call last)

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/serve.py", line 19, in get_response

    endpoint, renderer_instance = path_resolver.resolve()
                    ^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/path_resolver.py", line 68, in resolve

    renderer_instance = renderer(endpoint, self.http_status_code)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 45, in __init__

    self.set_template_path()
    ^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 54, in set_template_path

    app_path = frappe.get_app_path(app)
    ^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1483, in get_app_path

    return get_pymodule_path(app_name, *joins)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1513, in get_pymodule_path

    return abspath(join(dirname(get_module(scrub(modulename)).__file__ or ""), *joins))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1454, in get_module

    return importlib.import_module(modulename)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module

    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
    File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
    File "<frozen importlib._bootstrap>", line 1140, in _find_and_load_unlocked
    During handling of the above exception, another exception occurred:
    File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 124, in application

    response = get_response()
    ^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/serve.py", line 23, in get_response

    return handle_exception(e, endpoint, path, http_status_code)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/permissions.py", line 883, in wrapper

    return fn(e, *args, **kwargs)
    ^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/serve.py", line 37, in handle_exception

    return ErrorPage(exception=e).render()
    ^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/error_page.py", line 7, in __init__

    super().__init__(path=path, http_status_code=http_status_code)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 45, in __init__

    self.set_template_path()
    ^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 54, in set_template_path

    app_path = frappe.get_app_path(app)
    ^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1483, in get_app_path

    return get_pymodule_path(app_name, *joins)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1513, in get_pymodule_path

    return abspath(join(dirname(get_module(scrub(modulename)).__file__ or ""), *joins))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1454, in get_module

    return importlib.import_module(modulename)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module

    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
    File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
    File "<frozen importlib._bootstrap>", line 1140, in _find_and_load_unlocked
    During handling of the above exception, another exception occurred:
    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/serve.py", line 19, in get_response

    endpoint, renderer_instance = path_resolver.resolve()
                    ^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/path_resolver.py", line 68, in resolve

    renderer_instance = renderer(endpoint, self.http_status_code)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 45, in __init__

    self.set_template_path()
    ^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 54, in set_template_path

    app_path = frappe.get_app_path(app)
    ^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1483, in get_app_path

    return get_pymodule_path(app_name, *joins)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1513, in get_pymodule_path

    return abspath(join(dirname(get_module(scrub(modulename)).__file__ or ""), *joins))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1454, in get_module

    return importlib.import_module(modulename)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module

    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
    File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
    File "<frozen importlib._bootstrap>", line 1140, in _find_and_load_unlocked
    During handling of the above exception, another exception occurred:
    File "/home/frappe/frappe-bench/apps/frappe/frappe/middlewares.py", line 16, in __call__

    return super().__call__(environ, start_response)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/werkzeug/middleware/shared_data.py", line 250, in __call__

    return self.app(environ, start_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/werkzeug/middleware/shared_data.py", line 250, in __call__

    return self.app(environ, start_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 80, in application

    app(environ, start_response),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/werkzeug/wrappers/request.py", line 190, in application

    resp = f(*args[:-2] + (request,))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 133, in application

    response = handle_exception(e)
    ^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/permissions.py", line 883, in wrapper

    return fn(e, *args, **kwargs)
    ^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 396, in handle_exception

    response = get_response("message", http_status_code=http_status_code)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/serve.py", line 23, in get_response

    return handle_exception(e, endpoint, path, http_status_code)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/permissions.py", line 883, in wrapper

    return fn(e, *args, **kwargs)
    ^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/serve.py", line 37, in handle_exception

    return ErrorPage(exception=e).render()
    ^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/error_page.py", line 7, in __init__

    super().__init__(path=path, http_status_code=http_status_code)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 45, in __init__

    self.set_template_path()
    ^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 54, in set_template_path

    app_path = frappe.get_app_path(app)
    ^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1483, in get_app_path

    return get_pymodule_path(app_name, *joins)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1513, in get_pymodule_path

    return abspath(join(dirname(get_module(scrub(modulename)).__file__ or ""), *joins))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1454, in get_module

    return importlib.import_module(modulename)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module

    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
    File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
    File "<frozen importlib._bootstrap>", line 1140, in _find_and_load_unlocked

    ModuleNotFoundError: No module named 'mail'

The debugger caught an exception in your WSGI application. You can now look at the traceback which led to the error.

To switch between the interactive traceback and the plaintext one, you can click on the "Traceback" headline. From the text traceback you can also create a paste of it.

Now, does anyone know how I can get out of this situation? I just need mail to work and it feels like an unnecessary struggle.

Thanks.

1 Like

Hi @nicgentile,

This is a classic and frustrating issue, especially in a Docker environment. I’ve been there, and the good news is that your site is definitely recoverable.

The Root Cause

The ModuleNotFoundError: No module named 'mail' happens because your Frappe instance knows it’s supposed to load an app called mail (because it’s in your site’s apps.txt ), but it cannot find the actual app files within the container’s Python environment.

This creates a deadlock: Frappe won’t start because the module is missing, and you can’t run bench commands to uninstall it because Frappe won’t start. The key is to break this cycle, clean up, and then install it the correct way for Docker.

Step-by-Step Recovery Plan

Follow these steps in order to get your site back up and running with the Mail app properly installed.

Step 1: Get Your Site Running Again (Temporary Fix)

First, we need to get your backend container online so we can use the bench commands.

  1. Edit apps.txt : In your file system, navigate to your site’s folder and open the apps.txt file. The path will be something like: ./frappe-bench/sites/frontend/apps.txt
  2. Remove the ‘mail’ line: Find the line that simply says mail and delete it. Save the file.
  3. Restart Docker: Now, run docker-compose up -d . Your backend-1 container should start successfully because it’s no longer being asked to load a module it can’t find.

Your site should be accessible again, but the Mail app is not yet installed.

Step 2: Cleanly Uninstall the Broken App

Even though the app files were missing, the failed installation likely added tables to your database. We need to remove them cleanly.

  1. Access the Backend Container: Open a shell inside your now-running backend container:
docker-compose exec backend bash
  1. Run Uninstall: From inside the container, run the uninstall command for your site. This will remove any database tables and records related to the Mail app.
bench --site frontend uninstall-app mail

This might say the app isn’t installed, which is fine. We’re just ensuring a clean state.

Step 3: Properly Install the Mail App in Docker

Now we’ll do a fresh, correct installation.

  1. Get the App Code: While still inside the container, use bench to download the app’s source code into the container’s apps folder.
bench get-app https://github.com/frappe/mail
  1. Install on Your Site: Now, install the app on your frontend site. This will correctly add it to apps.txt and run the necessary database migrations.
bench --site frontend install-app mail
  1. Run Migrations: It’s always a good final step to ensure the database is up to date.
bench --site frontend migrate

At this point, the Mail app should be working perfectly within the running container.

Step 4: Make the Changes Permanent (The Crucial Docker Step)

The changes you just made will be lost if you ever recreate your containers. We need to bake the app’s files into your custom Docker image.

  1. Exit the Container: Type exit to return to your normal terminal.
  2. Rebuild Your Image: Tell Docker Compose to rebuild your backend image. This process will now include the mail app folder that you downloaded in the previous step.
docker-compose build backend
  1. Restart Your Services: Finally, bring everything up with the new, updated image.
docker-compose up -d

Your site is now stable and running with the Mail app correctly and permanently installed.

Best Practice for the Future

To avoid this problem, you should add the get-app command directly to your custom Dockerfile . This ensures the app’s source code is always part of the image when it’s built.

Example Dockerfile addition:

# ... (your other Dockerfile commands)

# This should come after you initialize the bench
RUN bench get-app https://github.com/frappe/mail

# ... (the rest of your Dockerfile)

Hope this helps you get out of the struggle!
Please let me know if you have any questions or need assistance applying this.

Best regards,
Tharun Kumar K
6381360779

Yeah, so, I have done this again, and the result is the same.

There are a couple of factors here I have noted. The greatest is that the bench process fails to solve dependency issues and that to me is a major issue. Secondly, regardless of how well I run the commands, how well I follow the script, the outcome is the same. It doesn’t make sense why. The installation is clean. I have tried it on a vanilla docker with just mail being the only thing to install and the outcome is the same. So, my argument becomes, the docker image I have is borked itself which is now another major issue.

Or, my build process is flawed, but, I followed this from github (frappe_docker/docs/custom-apps.md at main · frappe/frappe_docker · GitHub) so now I guess a problem there? If it is not the server, or a vanilla build, or mail itself, then the issue to me is either I messed the build process multiple times somehow, or the build files themselves are messed up.

My plan is to export my site, cause I already had built other things and I cannot afford to lose them, time wise, and then, somehow, rebuild and figure out what is going wrong, before I get to answer tougher questions.

That being said, thank you for really helping me with this.