diff --git a/include/libcamera/internal/mailbox.h b/include/libcamera/internal/mailbox.h
new file mode 100644
index 000000000..4c76eade9
--- /dev/null
+++ b/include/libcamera/internal/mailbox.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Google Inc.
+ *
+ * mailbox.h - Template class for generic mailbox
+ */
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include <libcamera/base/log.h>
+
+namespace libcamera {
+
+template<class T>
+class MailBox
+{
+public:
+	using Recycler = std::function<void(T &)>;
+
+	MailBox()
+		: valid_(false) {}
+	~MailBox()
+	{
+		if (valid_ && recycler_)
+			recycler_(item_);
+	}
+
+	void put(const T &item, Recycler recycler)
+	{
+		ASSERT(!valid_);
+
+		valid_ = true;
+		recycler_ = recycler;
+		item_ = item;
+	}
+
+	const T &get()
+	{
+		ASSERT(valid_);
+		return item_;
+	}
+
+	bool valid() { return valid_; }
+
+private:
+	T item_;
+	bool valid_;
+	std::function<void(T &)> recycler_;
+};
+
+template<class T>
+using SharedMailBox = std::shared_ptr<MailBox<T>>;
+
+template<class T>
+SharedMailBox<T> makeMailBox()
+{
+	return std::make_shared<MailBox<T>>();
+}
+
+template<class T>
+std::vector<SharedMailBox<T>> makeMailBoxVector(const unsigned int count)
+{
+	std::vector<SharedMailBox<T>> mailBoxes;
+	mailBoxes.resize(count);
+	for (unsigned int i = 0; i < count; i++)
+		mailBoxes[i] = makeMailBox<T>();
+
+	return mailBoxes;
+}
+
+} /* namespace libcamera */
diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index 1c5eef9ca..4c1228b95 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -27,6 +27,7 @@ libcamera_internal_headers = files([
     'ipa_proxy.h',
     'ipc_pipe.h',
     'ipc_unixsocket.h',
+    'mailbox.h',
     'mapped_framebuffer.h',
     'media_device.h',
     'media_object.h',
diff --git a/src/libcamera/mailbox.cpp b/src/libcamera/mailbox.cpp
new file mode 100644
index 000000000..7335122e3
--- /dev/null
+++ b/src/libcamera/mailbox.cpp
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Google Inc.
+ *
+ * mailbox.cpp - Template class for generic mailbox
+ */
+
+/**
+ * \class libcamera::MailBox
+ * \brief MailBox of data
+ *
+ * A MailBox contains a block of data that has a single producer, and one or
+ * multiple consumers. It's often used as a shared_ptr (SharedMailBox) and
+ * being held by different tasks in pipelines.
+ */
+
+/**
+ * \typedef MailBox::Recycler
+ * \brief A function that recycles the data when the mailbox is destructed
+ */
+
+/**
+ * \fn MailBox::put(const T &item, MailBox<T>::Recycler recycler)
+ * \brief Set the data as the producer. Should be called only once
+ * \param[in] item The data to be stored
+ * \param[in] recycler The function that recycles \a data when destructing the
+ * mailbox. Mostly used for recycling buffers.
+ */
+
+/**
+ * \fn MailBox::get()
+ * \brief Get the data as a consumer. put() function should be called ahead
+ *
+ * \return The stored data
+ */
+
+/**
+ * \fn MailBox::valid()
+ * \return True if put() function has been called
+ */
+
+/**
+ * \typedef libcamera::SharedMailBox
+ * \brief A mailbox as a shared_ptr
+ */
+
+/**
+ * \fn libcamera::makeMailBox()
+ * \brief A helper function to create a SharedMailBox
+ *
+ * \return A mailbox as a SharedMailBox
+ */
+
+/**
+ * \fn libcamera::makeMailBoxVector(const unsigned int count)
+ * \brief A helper function to create a list of mailboxes
+ * \param[in] count The number of mailboxes requested
+ *
+ * \return Mailboxes as a vector
+ */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index aa9ab0291..6c3ef5377 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -38,6 +38,7 @@ libcamera_internal_sources = files([
     'ipc_pipe.cpp',
     'ipc_pipe_unixsocket.cpp',
     'ipc_unixsocket.cpp',
+    'mailbox.cpp',
     'mapped_framebuffer.cpp',
     'media_device.cpp',
     'media_object.cpp',
