diff --git a/apps/cli/executables/delivery/delivery/deliverer.py b/apps/cli/executables/delivery/delivery/deliverer.py
index 73cb3af363f4279dc6251b08f3a90507c0d34785..06ee1101780e017b348824a25340ecb3bc40f321 100644
--- a/apps/cli/executables/delivery/delivery/deliverer.py
+++ b/apps/cli/executables/delivery/delivery/deliverer.py
@@ -5,7 +5,7 @@
 # -------------------------------------------------------------------------
 import pathlib
 
-from .destinations.interfaces import *
+from .destinations.interfaces import DestinationDecorator, DeliveryContextIF, Destination
 from .destinations.tar import TarArchiver
 from .destinations.local import LocalDestination
 from .destinations.checksum import ChecksumDecorator
diff --git a/apps/cli/executables/delivery/delivery/delivery.py b/apps/cli/executables/delivery/delivery/delivery.py
index 19fff561235d3aa9a32ce87add823d75f474c145..526b52ec38b271e607f0fcbd7a7e553a840a46ba 100644
--- a/apps/cli/executables/delivery/delivery/delivery.py
+++ b/apps/cli/executables/delivery/delivery/delivery.py
@@ -1,4 +1,6 @@
+import json
 import pathlib
+from typing import Dict
 
 from .context import DeliveryContext
 from .finder import HeuristicProductFinder, ProductFinder
@@ -23,7 +25,7 @@ class Delivery:
         else:
             return HeuristicProductFinder(source)
 
-    def deliver(self, context: DeliveryContext):
+    def deliver(self, context: DeliveryContext) -> Dict:
         """
         Primary entrypoint to delivery process
 
@@ -36,17 +38,26 @@ class Delivery:
         # loop over the products and deliver them
         with context.create_destination() as destination:
             for product in finder.find_products():
+                print(f"Delivering {product} to {destination}")
                 product.deliver_to(destination)
 
         # the last thing we should do is return the URL to the delivered stuff.
         # for a local delivery, this will be a file URL; otherwise it will be an http URL.
-        return destination.result_url()
+        return destination.results()
 
 
 def main(args=None):
     """CLI entry point"""
+    # parse the arguments
     context = DeliveryContext.parse_commandline(args)
-    print(Delivery().deliver(context))
+
+    # perform the delivery
+    delivery = Delivery().deliver(context)
+
+    # write the results to a file
+    with open("delivery.json", "w") as result_file:
+        # indent=2 causes the file to be pretty-printed for humans
+        json.dump(delivery, result_file, indent=2)
 
 
 if __name__ == "__main__":
diff --git a/apps/cli/executables/delivery/delivery/destinations/checksum.py b/apps/cli/executables/delivery/delivery/destinations/checksum.py
index ab85b8ae42e3c90f9a094b0a67e861aca6a57801..bd909f15de56e0b0190bc2ac29db0f4c5d51da99 100644
--- a/apps/cli/executables/delivery/delivery/destinations/checksum.py
+++ b/apps/cli/executables/delivery/delivery/destinations/checksum.py
@@ -57,3 +57,6 @@ class ChecksumDecorator(DestinationDecorator):
 
         # now proceed
         super().close()
+
+    def __str__(self):
+        return str(self.underlying) + " with SHA1SUM"
diff --git a/apps/cli/executables/delivery/delivery/destinations/fetchfile.py b/apps/cli/executables/delivery/delivery/destinations/fetchfile.py
index 089103fa1b8e409e374313781e6998b596877eb6..ebd5bafb73c96b8984f35b75f816964d36816aaf 100644
--- a/apps/cli/executables/delivery/delivery/destinations/fetchfile.py
+++ b/apps/cli/executables/delivery/delivery/destinations/fetchfile.py
@@ -22,9 +22,8 @@ class FetchFileGenerator(DestinationDecorator):
         super().add_file(file, relative_path)
 
         # add a line to the fetch script for this file
-        self.fetch_script.file().writelines(
-            [f"wget {self.underlying.result_url()}/{relative_path}\n".encode("utf8")]
-        )
+        url = self.underlying.results()["url"]
+        self.fetch_script.file().writelines([f"wget {url}/{relative_path}\n".encode("utf8")])
 
     def close(self):
         # first close the script
@@ -32,3 +31,6 @@ class FetchFileGenerator(DestinationDecorator):
 
         # proceed
         super().close()
+
+    def __str__(self):
+        return str(self.underlying) + " with fetch-all.sh"
diff --git a/apps/cli/executables/delivery/delivery/destinations/interfaces.py b/apps/cli/executables/delivery/delivery/destinations/interfaces.py
index 4a3a2ea0dddc007d512d6803126fd28544ce70ac..2df84387158c75218bbe8841edbdecc49da3b4c7 100644
--- a/apps/cli/executables/delivery/delivery/destinations/interfaces.py
+++ b/apps/cli/executables/delivery/delivery/destinations/interfaces.py
@@ -1,6 +1,6 @@
 import pathlib
 from abc import ABC, abstractmethod
-from typing import BinaryIO
+from typing import BinaryIO, Dict
 
 
 class DeliveryContextIF(ABC):
@@ -105,10 +105,16 @@ class Destination(ABC):
         pass
 
     @abstractmethod
-    def result_url(self) -> str:
+    def results(self) -> Dict:
         """
-        Returns a URL to the results.
-        :return:  URL pointing to the results
+        Returns some result information, to be returned to the caller. Expected keys include:
+
+        ``delivered_to``
+            A filesystem location where the delivery placed files
+        ``url``
+            A URL to the delivery location, if it can be accessed via a browser
+
+        :return: result information
         """
         pass
 
@@ -171,9 +177,12 @@ class DestinationDecorator(Destination):
         """
         self.underlying.close()
 
-    def result_url(self) -> str:
+    def results(self) -> Dict:
         """
         In most cases you should leave this alone unless you want to modify the URL that gets
         returned to the console at the end of delivery.
         """
-        return self.underlying.result_url()
+        return self.underlying.results()
+
+    def __str__(self):
+        return str(self.underlying)
diff --git a/apps/cli/executables/delivery/delivery/destinations/local.py b/apps/cli/executables/delivery/delivery/destinations/local.py
index 7302e2acc08a083f88993755533232b2afa8cd3b..7e99ba73ed04b104ef0de19cfcf4836720078f87 100644
--- a/apps/cli/executables/delivery/delivery/destinations/local.py
+++ b/apps/cli/executables/delivery/delivery/destinations/local.py
@@ -1,7 +1,7 @@
 import os
 import shutil
 import tempfile
-from typing import BinaryIO
+from typing import BinaryIO, Dict
 
 from .interfaces import Destination, DeliveryContextIF, DestinationTempFile
 import pathlib
@@ -50,11 +50,17 @@ class LocalDestination(Destination):
         # hand off to the LocalDestinationTempFile, which handles the rest
         return LocalDestinationTempFile(self, relative_path, rawfile)
 
-    def result_url(self) -> str:
+    def results(self) -> Dict:
         """
         Return a file:/// URL for this location
         """
-        return pathlib.Path(self.path.absolute()).as_uri()
+
+        # we could also supply a file:/// URL, which would be constructed like so:
+        # "url": pathlib.Path(self.path.absolute()).as_uri()
+        return {"delivered_to": str(self.path)}
+
+    def __str__(self):
+        return f"local destination {self.path}"
 
 
 class LocalDestinationTempFile(DestinationTempFile):
diff --git a/apps/cli/executables/delivery/delivery/destinations/sharedweb.py b/apps/cli/executables/delivery/delivery/destinations/sharedweb.py
index bd5b9ee34ad2d1035019a5ab5f17f1fe51764bb2..bb33831aa6ab3e037818daaacaa3af504855ceed 100644
--- a/apps/cli/executables/delivery/delivery/destinations/sharedweb.py
+++ b/apps/cli/executables/delivery/delivery/destinations/sharedweb.py
@@ -1,4 +1,5 @@
 import pathlib
+from typing import Dict
 
 from .interfaces import DestinationDecorator, DeliveryContextIF
 from .local import LocalDestination
@@ -22,16 +23,19 @@ class SharedWebDestination(DestinationDecorator):
         username = "anonymous"
 
         # determine the destination directory
-        destination_dir = pathlib.Path(capo.nraoDownloadDirectory) / username / context.token()
+        self.destination_dir = pathlib.Path(capo.nraoDownloadDirectory) / username / context.token()
 
         # LocalDestination won't create a directory, so we'll do that here
-        destination_dir.mkdir(parents=True)
+        self.destination_dir.mkdir(parents=True)
 
         # we're ready to build the local destination and thread it through our super constructor
-        super().__init__(LocalDestination(context, destination_dir))
+        super().__init__(LocalDestination(context, self.destination_dir))
 
         # generate the download URL for later usage
         self.download_url = f"{capo.nraoDownloadUrl}/{username}/{context.token()}"
 
-    def result_url(self) -> str:
-        return self.download_url
+    def results(self) -> Dict:
+        return {"delivered_to": str(self.destination_dir), "url": self.download_url}
+
+    def __str__(self):
+        return f"shared web root at {self.destination_dir}"
diff --git a/apps/cli/executables/delivery/delivery/destinations/tar.py b/apps/cli/executables/delivery/delivery/destinations/tar.py
index 141edd7a8f10ff03164d2ad155790522cf1216ee..672d79b9cfdd01b6b8877030c8e2846b1fe6f949 100644
--- a/apps/cli/executables/delivery/delivery/destinations/tar.py
+++ b/apps/cli/executables/delivery/delivery/destinations/tar.py
@@ -55,3 +55,6 @@ class TarArchiver(DestinationDecorator):
 
         # now we can proceed
         self.underlying.close()
+
+    def __str__(self):
+        return f"tar archive in " + str(self.underlying)
diff --git a/apps/cli/executables/delivery/delivery/products.py b/apps/cli/executables/delivery/delivery/products.py
index 4a4e46526a1329050bdde54e318248c6f1170004..7f1c5b66f39108d81c8046c91bc7d2b3df13f75d 100644
--- a/apps/cli/executables/delivery/delivery/products.py
+++ b/apps/cli/executables/delivery/delivery/products.py
@@ -45,10 +45,16 @@ class ExecutionBlock(SpooledProduct):
     eventually.
     """
 
+    @property
+    def eb_name(self):
+        return self.path.absolute().name
+
     def deliver_to(self, destination: Destination):
-        eb_name = self.path.absolute().name
         # let's use our directory name as the relative path
-        destination.add_directory(self.path, eb_name)
+        destination.add_directory(self.path, self.eb_name)
+
+    def __str__(self):
+        return f"execution block {self.eb_name}"
 
 
 # Future types of product that might be needed:
diff --git a/apps/cli/executables/delivery/test/test_api.py b/apps/cli/executables/delivery/test/test_api.py
index e23c4c56e0a39cf7335db453d9c00f1efcda5ac7..f275c8d0f26817752247e2217febf7604ed53d2a 100644
--- a/apps/cli/executables/delivery/test/test_api.py
+++ b/apps/cli/executables/delivery/test/test_api.py
@@ -60,6 +60,11 @@ def test_local_delivery_add_file(tmpdir, file_to_deliver: pathlib.Path, dest_dir
     # see if the content of the file is intact
     assert (dest_dir / file_to_deliver.name).read_text(encoding=None) == "content"
 
+    # see if the results are alright
+    results = local_dest.results()
+    assert len(results.keys()) == 1
+    assert results["delivered_to"] == str(dest_dir)
+
 
 def test_local_delivery_add_directory(
     tmpdir: pathlib.Path, file_to_deliver: pathlib.Path, dest_dir: pathlib.Path
@@ -71,9 +76,13 @@ def test_local_delivery_add_directory(
     local_dest.add_directory(file_to_deliver.parent, file_to_deliver.parent.name)
 
     compare_dirs = filecmp.dircmp(file_to_deliver.parent, dest_dir / "prior")
+
     # see if the destination got all the files from source
-    assert (
-        len(compare_dirs.left_only) == 0
-        and len(compare_dirs.right_only) == 0
-        and len(compare_dirs.funny_files) == 0
-    )
+    assert len(compare_dirs.left_only) == 0
+    assert len(compare_dirs.right_only) == 0
+    assert len(compare_dirs.funny_files) == 0
+
+    # see if the results are alright
+    results = local_dest.results()
+    assert len(results.keys()) == 1
+    assert results["delivered_to"] == str(dest_dir)
diff --git a/apps/cli/executables/delivery/test/test_cli.py b/apps/cli/executables/delivery/test/test_cli.py
index c6ab65578fcc103470ca5263c69d63f16ee0951f..98e555a99d3eafc6a668cbe349949948497723f5 100644
--- a/apps/cli/executables/delivery/test/test_cli.py
+++ b/apps/cli/executables/delivery/test/test_cli.py
@@ -1,5 +1,6 @@
 # Testing the CLI
 import filecmp
+import json
 import pathlib
 import shutil
 import tarfile
@@ -64,9 +65,16 @@ def test_local_rawdata_no_tar(tmpdir_factory):
     test_data_path = "../../../../shared/workspaces/test/test_data/spool/724126739/"
     eb_name = "17A-109.sb33151331.eb33786546.57892.65940042824"
     main(["-r", "-l", temp_directory, test_data_path])
+
     # compare the source and destination
     assert_directories_are_same(temp_directory + "/" + eb_name, (test_data_path + eb_name))
 
+    # ensure that we actually got a delivery file with the proper contents
+    with open("delivery.json", "r") as delivery_results_file:
+        results = json.load(delivery_results_file)
+        assert len(results.keys()) == 1
+        assert results["delivered_to"] == temp_directory
+
 
 def test_local_rawdata_with_tar(tmpdir_factory):
     """
@@ -86,6 +94,11 @@ def test_local_rawdata_with_tar(tmpdir_factory):
 
     verify_extracted_directory(eb_name, tar_path, temp_directory, test_data_path)
 
+    with open("delivery.json", "r") as delivery_results_file:
+        results = json.load(delivery_results_file)
+        assert len(results.keys()) == 1
+        assert results["delivered_to"] == str(temp_directory)
+
 
 # @pytest.mark.skip(reason="Test needs more dev time")
 def test_web_rawdata_no_tar(tmpdir_factory):
@@ -100,10 +113,11 @@ def test_web_rawdata_no_tar(tmpdir_factory):
         mocked_capo_settings.return_value.nraoDownloadDirectory = str(temp_directory)
         mocked_capo_settings.return_value.nraoDownloadUrl = "http://testing"
         assert str(temp_directory) == mocked_capo_settings().nraoDownloadDirectory
-        destination_url = Delivery().deliver(test_context)
+        results = Delivery().deliver(test_context)
 
-    # determine the destination by looking at the URL
-    actual_delivery_dir = temp_directory / destination_url.lstrip("http://testing")
+    # check the relationship between the delivery root and the URL
+    assert str(temp_directory / results["url"].lstrip("http://testing")) == results["delivered_to"]
+    actual_delivery_dir = pathlib.Path(results["delivered_to"])
 
     # compare the source and destination
     assert_directories_are_same(actual_delivery_dir / eb_name, f"{test_data_path}{eb_name}")
@@ -121,11 +135,12 @@ def test_web_rawdata_with_tar(tmpdir_factory):
         mocked_capo_settings.return_value.nraoDownloadDirectory = temp_directory
         mocked_capo_settings.return_value.nraoDownloadUrl = "http://testing"
         assert temp_directory == mocked_capo_settings().nraoDownloadDirectory
-        destination_url = Delivery().deliver(test_context)
+        results = Delivery().deliver(test_context)
     eb_name = "17A-109.sb33151331.eb33786546.57892.65940042824"
 
-    # determine the destination by looking at the URL
-    actual_delivery_dir = temp_directory / destination_url.lstrip("http://testing")
+    # check the relationship between the delivery root and the URL
+    assert str(temp_directory / results["url"].lstrip("http://testing")) == results["delivered_to"]
+    actual_delivery_dir = pathlib.Path(results["delivered_to"])
 
     # does a tar exist where we think
     tar_path = actual_delivery_dir / (eb_name + ".tar")