diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1dd183360d8db3bae9958b121a6bff7ef926e521..6c109c48c1afc043b5a46ac80a65c7acb8e13519 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.16)
 
 project(jami-core
-    VERSION 15.2.0
+    VERSION 15.3.0
     LANGUAGES C CXX)
 set(PACKAGE_NAME "Jami Daemon")
 set (CMAKE_CXX_STANDARD 17)
diff --git a/bin/osxmain.cpp b/bin/osxmain.cpp
index deb81ce0872d01dd99b7c7eb8359e75763cd003f..772245de47d55ca331858d184fc8d1de7fd2647a 100644
--- a/bin/osxmain.cpp
+++ b/bin/osxmain.cpp
@@ -26,6 +26,7 @@
 #include <getopt.h>
 #include <string>
 #include <chrono>
+#include <filesystem>
 
 #include "jami.h"
 #include "callmanager_interface.h"
@@ -194,13 +195,9 @@ signal_handler(int code)
 int
 main(int argc, char *argv [])
 {
-    // make a copy as we don't want to modify argv[0], copy it to a vector to
-    // guarantee that memory is correctly managed/exception safe
-    std::string programName {argv[0]};
-    std::vector<char> writable(programName.size() + 1);
-    std::copy(programName.begin(), programName.end(), writable.begin());
-
-    jami::fileutils::set_program_dir(writable.data());
+    // Set the program's directory path as the resource directory path.
+    std::filesystem::path programPath(argv[0]);
+    jami::fileutils::set_resource_dir_path(programPath.parent_path());
 
 #ifdef TOP_BUILDDIR
     if (!getenv("CODECS_PATH"))
diff --git a/bin/winmain.cpp b/bin/winmain.cpp
index f83254255d55cf2e25b10285a1f6f118497d7d2f..106cc9afddb632c47cae1989d46d19ae69f7db13 100644
--- a/bin/winmain.cpp
+++ b/bin/winmain.cpp
@@ -24,6 +24,7 @@
 #include <signal.h>
 #include <getopt.h>
 #include <string>
+#include <filesystem>
 
 #include "jami.h"
 #include "callmanager_interface.h"
@@ -210,13 +211,9 @@ signal_handler(int code)
 int
 main(int argc, char *argv [])
 {
-    // make a copy as we don't want to modify argv[0], copy it to a vector to
-    // guarantee that memory is correctly managed/exception safe
-    std::string programName {argv[0]};
-    std::vector<char> writable(programName.size() + 1);
-    std::copy(std::begin(programName), std::end(programName),std::begin(writable));
-
-    jami::fileutils::set_program_dir(writable.data());
+    // Set the program's directory path as the resource directory path.
+    std::filesystem::path programPath(argv[0]);
+    jami::fileutils::set_resource_dir_path(programPath.parent_path());
 
     print_title();
 
diff --git a/configure.ac b/configure.ac
index a06483b4471c68f77e7fd76306fe85bdddd60a0c..20fe517a59d38c353161d6625e93ad0c30694b30 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@ dnl Jami - configure.ac
 
 dnl Process this file with autoconf to produce a configure script.
 AC_PREREQ([2.69])
-AC_INIT([Jami Daemon],[15.2.0],[jami@gnu.org],[jami])
+AC_INIT([Jami Daemon],[15.3.0],[jami@gnu.org],[jami])
 
 dnl Clear the implicit flags that default to '-g -O2', otherwise they
 dnl take precedence over the values we set via the
diff --git a/meson.build b/meson.build
index 10f9de9795a11bb22fdedb82c13c1c40be7e25d6..f23e7d3b492b1e30c7bdcd43e2e4ad8971f58724 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
 project('jami-daemon', ['c', 'cpp'],
-        version: '15.2.0',
+        version: '15.3.0',
         license: 'GPL3+',
         default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'],
         meson_version:'>= 0.56'
diff --git a/src/account.cpp b/src/account.cpp
index b2b8767d3c7edacc587f2df0f07fe2270c78cb6d..3c6617ff5444e621bebc685d9408e6e75d95dd51 100644
--- a/src/account.cpp
+++ b/src/account.cpp
@@ -157,7 +157,10 @@ Account::loadDefaultCodecs()
 void
 Account::loadConfig() {
     setActiveCodecs(config_->activeCodecs);
-    auto ringtoneDir = fmt::format("{}/{}", JAMI_DATADIR, RINGDIR);
+
+    // Try to get the client-defined resource base directory, if any. If not set, use the default
+    // JAMI_DATADIR that was set at build time.
+    auto ringtoneDir = fileutils::get_resource_dir_path() / RINGDIR;
     ringtonePath_ = fileutils::getFullPath(ringtoneDir, config_->ringtonePath);
     // If the user defined a custom ringtone, the file may not exists
     // In this case, fallback on the default ringtone path
diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp
index b98dcbd89123bb83dae8b3a14b6784110edf2004..2ea5644db6b89688e11425e7c604b374d7ba5488 100644
--- a/src/client/configurationmanager.cpp
+++ b/src/client/configurationmanager.cpp
@@ -1159,4 +1159,10 @@ isAllModerators(const std::string& accountId)
     return jami::Manager::instance().isAllModerators(accountId);
 }
 
+void
+setResourceDirPath(const std::string& resourceDir)
+{
+    jami::fileutils::set_resource_dir_path(resourceDir);
+}
+
 } // namespace libjami
diff --git a/src/fileutils.cpp b/src/fileutils.cpp
index 3a04ec8448db5bf75fb271b2f85a9f2d40a5c387..ea26aecaa1f5f2d414c8b0070cfe168710f44085 100644
--- a/src/fileutils.cpp
+++ b/src/fileutils.cpp
@@ -14,12 +14,13 @@
  *  You should have received a copy of the GNU General Public License
  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 
-#include "logger.h"
 #include "fileutils.h"
+#include "logger.h"
 #include "archiver.h"
 #include "compiler_intrinsics.h"
 #include "base64.h"
@@ -119,6 +120,21 @@ winGetEnv(const wchar_t* name)
 namespace jami {
 namespace fileutils {
 
+static std::filesystem::path resource_dir_path;
+
+void
+set_resource_dir_path(const std::filesystem::path&  resourceDirPath)
+{
+    resource_dir_path = resourceDirPath;
+}
+
+const std::filesystem::path&
+get_resource_dir_path()
+{
+    static const std::filesystem::path jami_default_data_dir(JAMI_DATADIR);
+    return resource_dir_path.empty() ? jami_default_data_dir : resource_dir_path;
+}
+
 std::string
 expand_path(const std::string& path)
 {
@@ -411,20 +427,6 @@ writeArchive(const std::string& archive_str,
     }
 }
 
-#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
-#else
-static char* program_dir = NULL;
-void
-set_program_dir(char* program_path)
-{
-#ifdef _MSC_VER
-    _splitpath(program_path, nullptr, program_dir, nullptr, nullptr);
-#else
-    program_dir = dirname(program_path);
-#endif
-}
-#endif
-
 std::filesystem::path
 get_cache_dir(const char* pkg)
 {
@@ -473,7 +475,7 @@ get_home_dir_impl()
     if (SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_PROFILE, nullptr, 0, path))) {
         return jami::to_string(path);
     }
-    return program_dir;
+    return {};
 #else
 
     // 1) try getting user's home directory from the environment
diff --git a/src/fileutils.h b/src/fileutils.h
index cb9e518cb7b882d3c36820605fa0a0993133a6a1..b5dbeb0786132b48f793ec91a6fbee95155b21a3 100644
--- a/src/fileutils.h
+++ b/src/fileutils.h
@@ -55,12 +55,23 @@ const std::filesystem::path& get_data_dir();
 const std::filesystem::path& get_cache_dir();
 
 /**
- * Check directory existence and create it with given mode if it doesn't.
- * @param path to check, relative or absolute
- * @param dir last directory creation mode
- * @param parents default mode for all created directories except the last
+ * Set the program's resource directory path. This is used for clients that may be installed in different
+ * locations and are deployed with ringtones and other resources in an application relative directory.
+ * @param resource_dir_path The path to the ringtone directory.
+ */
+LIBJAMI_PUBLIC void set_resource_dir_path(const std::filesystem::path& resourceDirPath);
+
+/**
+ * Get the resource directory path that was set with set_resource_dir_path.
+ * @return The resource directory path.
+ */
+const std::filesystem::path& get_resource_dir_path();
+
+/**
+ * Expand the given path.
+ * @param path The path to be expanded.
+ * @return The expanded path as a string.
  */
-LIBJAMI_PUBLIC void set_program_dir(char* program_path); // public because bin/main.cpp uses it
 std::string expand_path(const std::string& path);
 
 bool isPathRelative(const std::filesystem::path& path);
diff --git a/src/jami/configurationmanager_interface.h b/src/jami/configurationmanager_interface.h
index d0e9f3eb9d6ad88de51d3e17708f29490308abf9..8d841ff582b3cf0daefdec74d9162e3164cd8d8b 100644
--- a/src/jami/configurationmanager_interface.h
+++ b/src/jami/configurationmanager_interface.h
@@ -329,6 +329,11 @@ LIBJAMI_PUBLIC void setAllModerators(const std::string& accountId, bool allModer
  */
 LIBJAMI_PUBLIC bool isAllModerators(const std::string& accountId);
 
+/**
+ * Set the resource directory path
+ */
+LIBJAMI_PUBLIC void setResourceDirPath(const std::string& resourceDirPath);
+
 struct LIBJAMI_PUBLIC AudioSignal
 {
     struct LIBJAMI_PUBLIC DeviceEvent