diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml
index 17b96f7..6db50b2 100644
--- a/.github/workflows/smoke-test.yml
+++ b/.github/workflows/smoke-test.yml
@@ -38,4 +38,41 @@ jobs:
         pip list
     - name: Run unit tests with pytest /  pytest-copie
       run: |
-        python -m pytest
\ No newline at end of file
+        python -m pytest
+    - name: Send status to Slack app
+      if: ${{ failure() && github.event_name != 'workflow_dispatch' }}
+      id: slack
+      uses: slackapi/slack-github-action@v1
+      with:
+        payload: |
+          {
+            "blocks": [
+              {
+                "type": "header",
+                "text": {
+                  "type": "plain_text",
+                  "text": "${{ github.repository }}"
+                }
+              },
+              {
+                "type": "section",
+                "text": {
+                  "type": "mrkdwn",
+                  "text": "GitHub Action build result: *${{ job.status }}* :${{ job.status }}:"
+                }
+              },
+              {
+                "type": "divider"
+              },
+              {
+                "type": "section",
+                "text": {
+                  "type": "mrkdwn",
+                  "text": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
+                }
+              }
+            ]
+          }
+      env:
+        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+        SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
\ No newline at end of file
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..dcc385e
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,93 @@
+repos:
+    # Prevents committing directly branches named 'main' and 'master'.
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.4.0
+    hooks:
+      - id: no-commit-to-branch
+        name: Prevent main branch commits
+        description: Prevent the user from committing directly to the primary branch.
+      - id: check-added-large-files
+        name: Check for large files
+        description: Prevent the user from committing very large files.
+        args: ['--maxkb=500']
+    # Verify that pyproject.toml is well formed
+  - repo: https://github.com/abravalheri/validate-pyproject
+    rev: v0.12.1
+    hooks:
+      - id: validate-pyproject
+        name: Validate pyproject.toml
+        description: Verify that pyproject.toml adheres to the established schema.
+    # Verify that GitHub workflows are well formed
+  - repo: https://github.com/python-jsonschema/check-jsonschema
+    rev: 0.28.0
+    hooks:
+      - id: check-github-workflows
+        args: ["--verbose"]
+    # Automatically sort the imports used in .py files
+  - repo: https://github.com/pycqa/isort
+    rev: 5.12.0
+    hooks:
+      - id: isort
+        name: Run isort
+        description: Sort and organize imports in .py and .pyi files.
+        types_or: [python, pyi]
+    # Analyze the tests code style and report code that doesn't adhere.
+  - repo: local
+    hooks:
+      - id: pylint
+        name: pylint (python files in tests/ and benchmarks/)
+        entry: pylint
+        language: system
+        types: [python]
+        files: ^(tests|benchmarks)/
+        args:
+          [
+            "-rn", # Only display messages
+            "-sn", # Don't display the score
+          ]
+    # Analyze the code style and report code that doesn't adhere.
+  - repo: https://github.com/psf/black
+    rev: 23.7.0
+    hooks:
+      - id: black-jupyter
+        name: Format code using black
+        types_or: [python, pyi, jupyter]
+        # It is recommended to specify the latest version of Python
+        # supported by your project here, or alternatively use
+        # pre-commit's default_language_version, see
+        # https://pre-commit.com/#top_level-default_language_version
+        language_version: python3.10
+    # Make sure Sphinx can build the documentation without issues.
+  - repo: local
+    hooks:
+      - id: sphinx-build
+        name: Build documentation with Sphinx
+        entry: sphinx-build
+        language: system
+        always_run: true
+        exclude_types: [file, symlink]
+        args:
+          [
+            "-T", # Show full trace back on exception
+            "-E", # Don't use saved env. always read all files.
+            "-b", # Flag to select which builder to use
+            "html", # Use the HTML builder
+            "-d", # Flag for cached environment and doctrees
+            "./docs/_build/doctrees", # directory
+            "./docs", # Source directory of documents
+            "./_readthedocs", # Output directory for rendered documents.
+          ]
+    # Run unit tests, verify that they pass. Note that coverage is run against
+    # the ./src directory here because that is what will be committed. In the
+    # github workflow script, the coverage is run against the installed package
+    # and uploaded to Codecov by calling pytest like so:
+    # `python -m pytest --cov=<package_name> --cov-report=xml`
+  - repo: local
+    hooks:
+      - id: pytest-check
+        name: Run unit tests
+        description: Run unit tests with pytest.
+        entry: bash -c "if python -m pytest --co -qq; then python -m pytest --cov=./src --cov-report=html; fi"
+        language: system
+        pass_filenames: false
+        always_run: true
diff --git a/tests/test_package_creation.py b/tests/test_package_creation.py
index a83c9ca..0cff52a 100644
--- a/tests/test_package_creation.py
+++ b/tests/test_package_creation.py
@@ -1,4 +1,5 @@
-import pytest
+"""Verify package creation using `pytest-copie`"""
+
 import subprocess
 
 import pytest
@@ -10,6 +11,8 @@ def successfully_created_project(result):
 
 
 def contains_required_files(result):
+    """Utility method to confirm that the required project files exist in the
+    copier result."""
     required_files = [
         ".copier-answers.yml",
         ".git_archival.txt",
@@ -42,6 +45,7 @@ def black_runs_successfully(result):
     black_results = subprocess.run(
         ["python", "-m", "black", "--check", (result.project_dir / "src")],
         cwd=result.project_dir,
+        check=False,
     )
 
     return black_results.returncode == 0
@@ -60,19 +64,20 @@ def pylint_runs_successfully(result):
             (result.project_dir / "src"),
         ],
         cwd=result.project_dir,
+        check=False,
     )
 
     return pylint_results.returncode == 0
 
 
-def unit_tests_in_project_run_successfully(result, package_name="example_package"):
+def unit_tests_in_project_run_successfully(result):
     """Test to ensure that the unit tests run successfully on the project
 
     !!! NOTE - This doesn't currently work because we need to `pip install` the hydrated
     project before running the tests. And we don't have a way to create a temporary
     virtual environment for the project.
     """
-    pytest_results = subprocess.run(["python", "-m", "pytest"], cwd=result.project_dir)
+    pytest_results = subprocess.run(["python", "-m", "pytest"], cwd=result.project_dir, check=False)
 
     return pytest_results.returncode == 0
 
@@ -106,7 +111,7 @@ def docs_build_successfully(result):
 def github_workflows_are_valid(result):
     """Test to ensure that the GitHub workflows are valid"""
     workflows_results = subprocess.run(
-        ["pre-commit", "run", "check-github-workflows"], cwd=result.project_dir
+        ["pre-commit", "run", "check-github-workflows"], cwd=result.project_dir, check=False
     )
     return workflows_results.returncode == 0
 
@@ -131,7 +136,7 @@ def test_all_defaults(copie):
 
     # check to see if the README file was hydrated with copier answers.
     found_line = False
-    with open(result.project_dir / "README.md") as f:
+    with open(result.project_dir / "README.md", encoding="utf-8") as f:
         for line in f:
             if "example_project" in line:
                 found_line = True
@@ -164,7 +169,7 @@ def test_use_black_and_no_example_modules(copie):
 
     # check to see if the pyproject.toml file has the expected dependencies
     found_line = False
-    with open(result.project_dir / "pyproject.toml") as f:
+    with open(result.project_dir / "pyproject.toml", encoding="utf-8") as f:
         for line in f:
             if '"black", # Used for static linting of files' in line:
                 found_line = True
