diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 35f295eded75049844e5fe43942a8a6a959e5dac..a7b7fa3ca76f6b8ccf8d5205785b047ec055029e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -7,6 +7,8 @@
 list (APPEND Source_Files
       "${CMAKE_CURRENT_SOURCE_DIR}/account.cpp"
       "${CMAKE_CURRENT_SOURCE_DIR}/account.h"
+      "${CMAKE_CURRENT_SOURCE_DIR}/account_config.cpp"
+      "${CMAKE_CURRENT_SOURCE_DIR}/account_config.h"
       "${CMAKE_CURRENT_SOURCE_DIR}/account_factory.cpp"
       "${CMAKE_CURRENT_SOURCE_DIR}/account_factory.h"
       "${CMAKE_CURRENT_SOURCE_DIR}/account_schema.h"
diff --git a/src/account.cpp b/src/account.cpp
index 21c59ce8aebc6acb19a9b058688e4c3332f029a8..6edebfd6551bbb953f2a1e34fe5d7bdb9cd7df7f 100644
--- a/src/account.cpp
+++ b/src/account.cpp
@@ -63,73 +63,19 @@ using namespace std::literals;
 
 namespace jami {
 
-const char* const Account::ALL_CODECS_KEY = "allCodecs";
-const char* const Account::VIDEO_CODEC_ENABLED = "enabled";
-const char* const Account::VIDEO_CODEC_NAME = "name";
-const char* const Account::VIDEO_CODEC_PARAMETERS = "parameters";
-const char* const Account::VIDEO_CODEC_BITRATE = "bitrate";
-const char* const Account::RINGTONE_PATH_KEY = "ringtonePath";
-const char* const Account::RINGTONE_ENABLED_KEY = "ringtoneEnabled";
-const char* const Account::VIDEO_ENABLED_KEY = "videoEnabled";
-const char* const Account::DISPLAY_NAME_KEY = "displayName";
-const char* const Account::ALIAS_KEY = "alias";
-const char* const Account::TYPE_KEY = "type";
-const char* const Account::ID_KEY = "id";
-const char* const Account::USERNAME_KEY = "username";
-const char* const Account::AUTHENTICATION_USERNAME_KEY = "authenticationUsername";
-const char* const Account::PASSWORD_KEY = "password";
-const char* const Account::HOSTNAME_KEY = "hostname";
-const char* const Account::ACCOUNT_ENABLE_KEY = "enable";
-const char* const Account::ACCOUNT_AUTOANSWER_KEY = "autoAnswer";
-const char* const Account::ACCOUNT_READRECEIPT_KEY = "sendReadReceipt";
-const char* const Account::ACCOUNT_ISRENDEZVOUS_KEY = "rendezVous";
-const char* const Account::ACCOUNT_ACTIVE_CALL_LIMIT_KEY = "activeCallLimit";
-const char* const Account::MAILBOX_KEY = "mailbox";
-const char* const Account::USER_AGENT_KEY = "useragent";
-const char* const Account::HAS_CUSTOM_USER_AGENT_KEY = "hasCustomUserAgent";
-const char* const Account::PRESENCE_MODULE_ENABLED_KEY = "presenceModuleEnabled";
-const char* const Account::UPNP_ENABLED_KEY = "upnpEnabled";
-const char* const Account::ACTIVE_CODEC_KEY = "activeCodecs";
-const std::string Account::DEFAULT_USER_AGENT = Account::setDefaultUserAgent();
-const char* const Account::DEFAULT_MODERATORS_KEY = "defaultModerators";
-const char* const Account::LOCAL_MODERATORS_ENABLED_KEY = "localModeratorsEnabled";
-const char* const Account::ALL_MODERATORS_ENABLED_KEY = "allModeratorsEnabled";
-const char* const Account::PROXY_PUSH_TOKEN_KEY = "proxyPushToken";
-const char* const Account::PROXY_PUSH_IOS_TOPIC_KEY = "proxyPushiOSTopic";
-
-// For portability, do not specify the absolute file name of the ring
-// tone.  Instead, specify its base name to be looked in
+// For portability, do not specify the absolute file name of the ringtone. 
+// Instead, specify its base name to be looked in
 // JAMI_DATADIR/ringtones/, where JAMI_DATADIR is a preprocessor macro denoting
 // the data directory prefix that must be set at build time.
-constexpr const char* const DEFAULT_RINGTONE_PATH = "default.opus";
+const std::string Account::DEFAULT_USER_AGENT = Account::getDefaultUserAgent();
 
 Account::Account(const std::string& accountID)
     : rand(dht::crypto::getSeededRandomEngine<std::mt19937_64>())
     , accountID_(accountID)
-    , username_()
-    , hostname_()
-    , alias_()
-    , enabled_(true)
-    , autoAnswerEnabled_(false)
-    , sendReadReceipt_(true)
-    , isRendezVous_(false)
     , registrationState_(RegistrationState::UNREGISTERED)
     , systemCodecContainer_(getSystemCodecContainer())
     , accountCodecInfoList_()
-    , ringtonePath_(DEFAULT_RINGTONE_PATH)
-    , ringtoneEnabled_(true)
-    , displayName_("")
-    , customUserAgent_("")
-    , hasCustomUserAgent_(false)
-    , mailBox_()
-    , upnpEnabled_(true)
-    , localModeratorsEnabled_(true)
-    , allModeratorsEnabled_(true)
-#if defined(__linux__) || defined(WIN32) || defined(__APPLE__)
-    , multiStreamEnabled_(true)
-#else
-    , multiStreamEnabled_(false)
-#endif
+    , ringtonePath_()
 {
     // Initialize the codec order, used when creating a new account
     loadDefaultCodecs();
@@ -149,7 +95,7 @@ Account::updateUpnpController()
 {
     std::lock_guard<std::mutex> lk {upnp_mtx};
 
-    if (not upnpEnabled_ or not isUsable()) {
+    if (not config().upnpEnabled or not isUsable()) {
         upnpCtrl_.reset();
         return;
     }
@@ -211,156 +157,23 @@ Account::loadDefaultCodecs()
 }
 
 void
-Account::serialize(YAML::Emitter& out) const
-{
-    const auto& activeCodecs = fmt::format("{}", fmt::join(getActiveCodecs(MEDIA_ALL), "/"sv));
-
-    out << YAML::Key << ID_KEY << YAML::Value << accountID_;
-    out << YAML::Key << ALIAS_KEY << YAML::Value << alias_;
-    out << YAML::Key << ACCOUNT_ENABLE_KEY << YAML::Value << enabled_;
-    out << YAML::Key << TYPE_KEY << YAML::Value << getAccountType();
-    out << YAML::Key << ACTIVE_CODEC_KEY << YAML::Value << activeCodecs;
-    out << YAML::Key << MAILBOX_KEY << YAML::Value << mailBox_;
-    out << YAML::Key << ACCOUNT_AUTOANSWER_KEY << YAML::Value << autoAnswerEnabled_;
-    out << YAML::Key << ACCOUNT_READRECEIPT_KEY << YAML::Value << sendReadReceipt_;
-    out << YAML::Key << ACCOUNT_ISRENDEZVOUS_KEY << YAML::Value << isRendezVous_;
-    out << YAML::Key << ACCOUNT_ACTIVE_CALL_LIMIT_KEY << YAML::Value << activeCallLimit_;
-    out << YAML::Key << RINGTONE_ENABLED_KEY << YAML::Value << ringtoneEnabled_;
-    out << YAML::Key << RINGTONE_PATH_KEY << YAML::Value << ringtonePath_;
-    out << YAML::Key << HAS_CUSTOM_USER_AGENT_KEY << YAML::Value << hasCustomUserAgent_;
-    out << YAML::Key << USER_AGENT_KEY << YAML::Value << customUserAgent_;
-    out << YAML::Key << DISPLAY_NAME_KEY << YAML::Value << displayName_;
-    out << YAML::Key << HOSTNAME_KEY << YAML::Value << hostname_;
-    out << YAML::Key << UPNP_ENABLED_KEY << YAML::Value << upnpEnabled_;
-    out << YAML::Key << DEFAULT_MODERATORS_KEY << YAML::Value << string_join(defaultModerators_);
-    out << YAML::Key << LOCAL_MODERATORS_ENABLED_KEY << YAML::Value << localModeratorsEnabled_;
-    out << YAML::Key << ALL_MODERATORS_ENABLED_KEY << YAML::Value << allModeratorsEnabled_;
-    out << YAML::Key << PROXY_PUSH_TOKEN_KEY << YAML::Value << deviceKey_;
-    out << YAML::Key << PROXY_PUSH_IOS_TOPIC_KEY << YAML::Value << notificationTopic_;
-}
-
-void
-Account::unserialize(const YAML::Node& node)
-{
-    using yaml_utils::parseValue;
-    using yaml_utils::parseValueOptional;
-
-    parseValue(node, ALIAS_KEY, alias_);
-    parseValue(node, ACCOUNT_ENABLE_KEY, enabled_);
-    parseValue(node, ACCOUNT_AUTOANSWER_KEY, autoAnswerEnabled_);
-    parseValueOptional(node, ACCOUNT_READRECEIPT_KEY, sendReadReceipt_);
-    parseValueOptional(node, ACCOUNT_ISRENDEZVOUS_KEY, isRendezVous_);
-    parseValue(node, ACCOUNT_ACTIVE_CALL_LIMIT_KEY, activeCallLimit_);
-    // parseValue(node, PASSWORD_KEY, password_);
-
-    parseValue(node, MAILBOX_KEY, mailBox_);
-
-    std::string activeCodecs;
-    if (parseValueOptional(node, ACTIVE_CODEC_KEY, activeCodecs))
-        setActiveCodecs(split_string_to_unsigned(activeCodecs, '/'));
-    else {
-        std::string allCodecs;
-        if (parseValueOptional(node, ALL_CODECS_KEY, allCodecs)) {
-            JAMI_WARN("Converting deprecated codec list");
-            auto list = convertIdToAVId(split_string_to_unsigned(allCodecs, '/'));
-            auto codec = searchCodecByName("H265", MEDIA_ALL);
-            // set H265 as first active codec if found
-            if (codec)
-                list.emplace(list.begin(), codec->systemCodecInfo.id);
-            setActiveCodecs(list);
-            runOnMainThread([id = getAccountID()] {
-                if (auto sthis = Manager::instance().getAccount(id))
-                    Manager::instance().saveConfig(sthis);
-            });
-        }
+Account::loadConfig() {
+    setActiveCodecs(config_->activeCodecs);
+    auto ringtoneDir = fmt::format("{}/{}", JAMI_DATADIR, 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
+    if (!fileutils::isFile(ringtonePath_)) {
+        JAMI_WARNING("Ringtone {} is not a valid file", ringtonePath_);
+        ringtonePath_ = fileutils::getFullPath(ringtoneDir, DEFAULT_RINGTONE_PATH);;
     }
-
-    parseValue(node, DISPLAY_NAME_KEY, displayName_);
-    parseValue(node, HOSTNAME_KEY, hostname_);
-
-    parseValue(node, HAS_CUSTOM_USER_AGENT_KEY, hasCustomUserAgent_);
-    parseValue(node, USER_AGENT_KEY, customUserAgent_);
-    parseValue(node, RINGTONE_PATH_KEY, ringtonePath_);
-    parseValue(node, RINGTONE_ENABLED_KEY, ringtoneEnabled_);
-    if (ringtonePath_.empty()) {
-        ringtonePath_ = DEFAULT_RINGTONE_PATH;
-    } else {
-        // If the user defined a custom ringtone, the file may not exists
-        // In this case, fallback on the default ringtone path (this will be set during the next
-        // setAccountDetails)
-        auto pathRingtone = fmt::format("{}/{}/{}", JAMI_DATADIR, RINGDIR, ringtonePath_);
-        if (!fileutils::isFile(ringtonePath_) && !fileutils::isFile(pathRingtone)) {
-            JAMI_WARN("Ringtone %s is not a valid file", pathRingtone.c_str());
-            ringtonePath_ = DEFAULT_RINGTONE_PATH;
-        }
-    }
-
-    parseValue(node, UPNP_ENABLED_KEY, upnpEnabled_);
     updateUpnpController();
-
-    std::string defMod;
-    parseValueOptional(node, DEFAULT_MODERATORS_KEY, defMod);
-    defaultModerators_ = string_split_set(defMod);
-    parseValueOptional(node, LOCAL_MODERATORS_ENABLED_KEY, localModeratorsEnabled_);
-    parseValueOptional(node, ALL_MODERATORS_ENABLED_KEY, allModeratorsEnabled_);
-    parseValueOptional(node, PROXY_PUSH_TOKEN_KEY, deviceKey_);
-    parseValueOptional(node, PROXY_PUSH_IOS_TOPIC_KEY, notificationTopic_);
 }
 
 void
-Account::setAccountDetails(const std::map<std::string, std::string>& details)
+Account::saveConfig() const
 {
-    // Account setting common to any account type
-    parseString(details, Conf::CONFIG_ACCOUNT_ALIAS, alias_);
-    parseString(details, Conf::CONFIG_ACCOUNT_DISPLAYNAME, displayName_);
-    parseBool(details, Conf::CONFIG_ACCOUNT_ENABLE, enabled_);
-    parseString(details, Conf::CONFIG_ACCOUNT_HOSTNAME, hostname_);
-    parseString(details, Conf::CONFIG_ACCOUNT_MAILBOX, mailBox_);
-    parseBool(details, Conf::CONFIG_ACCOUNT_AUTOANSWER, autoAnswerEnabled_);
-    parseBool(details, Conf::CONFIG_ACCOUNT_SENDREADRECEIPT, sendReadReceipt_);
-    parseBool(details, Conf::CONFIG_ACCOUNT_ISRENDEZVOUS, isRendezVous_);
-    parseInt(details, libjami::Account::ConfProperties::ACTIVE_CALL_LIMIT, activeCallLimit_);
-    parseBool(details, Conf::CONFIG_RINGTONE_ENABLED, ringtoneEnabled_);
-    parseString(details, Conf::CONFIG_RINGTONE_PATH, ringtonePath_);
-    if (ringtonePath_.empty()) {
-        ringtonePath_ = DEFAULT_RINGTONE_PATH;
-    }
-    parseBool(details, Conf::CONFIG_ACCOUNT_HAS_CUSTOM_USERAGENT, hasCustomUserAgent_);
-    if (hasCustomUserAgent_)
-        parseString(details, Conf::CONFIG_ACCOUNT_USERAGENT, customUserAgent_);
-
-    parseBool(details, Conf::CONFIG_UPNP_ENABLED, upnpEnabled_);
-    updateUpnpController();
-
-    std::string defMod;
-    parseString(details, Conf::CONFIG_DEFAULT_MODERATORS, defMod);
-    defaultModerators_ = string_split_set(defMod);
-    parseBool(details, Conf::CONFIG_LOCAL_MODERATORS_ENABLED, localModeratorsEnabled_);
-    parseBool(details, Conf::CONFIG_ALL_MODERATORS_ENABLED, allModeratorsEnabled_);
-}
-
-std::map<std::string, std::string>
-Account::getAccountDetails() const
-{
-    return {{Conf::CONFIG_ACCOUNT_ALIAS, alias_},
-            {Conf::CONFIG_ACCOUNT_DISPLAYNAME, displayName_},
-            {Conf::CONFIG_ACCOUNT_ENABLE, enabled_ ? TRUE_STR : FALSE_STR},
-            {Conf::CONFIG_ACCOUNT_TYPE, getAccountType()},
-            {Conf::CONFIG_ACCOUNT_HOSTNAME, hostname_},
-            {Conf::CONFIG_ACCOUNT_USERNAME, username_},
-            {Conf::CONFIG_ACCOUNT_MAILBOX, mailBox_},
-            {Conf::CONFIG_ACCOUNT_USERAGENT, customUserAgent_},
-            {Conf::CONFIG_ACCOUNT_HAS_CUSTOM_USERAGENT, hasCustomUserAgent_ ? TRUE_STR : FALSE_STR},
-            {Conf::CONFIG_ACCOUNT_AUTOANSWER, autoAnswerEnabled_ ? TRUE_STR : FALSE_STR},
-            {Conf::CONFIG_ACCOUNT_SENDREADRECEIPT, sendReadReceipt_ ? TRUE_STR : FALSE_STR},
-            {Conf::CONFIG_ACCOUNT_ISRENDEZVOUS, isRendezVous_ ? TRUE_STR : FALSE_STR},
-            {libjami::Account::ConfProperties::ACTIVE_CALL_LIMIT, std::to_string(activeCallLimit_)},
-            {Conf::CONFIG_RINGTONE_ENABLED, ringtoneEnabled_ ? TRUE_STR : FALSE_STR},
-            {Conf::CONFIG_RINGTONE_PATH, ringtonePath_},
-            {Conf::CONFIG_UPNP_ENABLED, upnpEnabled_ ? TRUE_STR : FALSE_STR},
-            {Conf::CONFIG_DEFAULT_MODERATORS, string_join(defaultModerators_)},
-            {Conf::CONFIG_LOCAL_MODERATORS_ENABLED, localModeratorsEnabled_ ? TRUE_STR : FALSE_STR},
-            {Conf::CONFIG_ALL_MODERATORS_ENABLED, allModeratorsEnabled_ ? TRUE_STR : FALSE_STR}};
+    Manager::instance().saveConfig();
 }
 
 std::map<std::string, std::string>
@@ -408,40 +221,6 @@ Account::sortCodec()
                  const std::shared_ptr<AccountCodecInfo>& b) { return a->order < b->order; });
 }
 
-std::vector<unsigned>
-Account::convertIdToAVId(const std::vector<unsigned>& list)
-{
-#if !(defined(TARGET_OS_IOS) && TARGET_OS_IOS)
-    constexpr size_t CODEC_NUM = 12;
-#else
-    constexpr size_t CODEC_NUM = 10;
-#endif
-
-    static constexpr std::array<unsigned, CODEC_NUM> CODEC_ID_MAPPING
-        = { AV_CODEC_ID_NONE,
-            AV_CODEC_ID_H264,
-            AV_CODEC_ID_VP8,
-#if !(defined(TARGET_OS_IOS) && TARGET_OS_IOS)
-            AV_CODEC_ID_MPEG4,
-            AV_CODEC_ID_H263,
-#endif
-            AV_CODEC_ID_OPUS,
-            AV_CODEC_ID_ADPCM_G722,
-            AV_CODEC_ID_SPEEX | 0x20000000,
-            AV_CODEC_ID_SPEEX | 0x10000000,
-            AV_CODEC_ID_SPEEX,
-            AV_CODEC_ID_PCM_ALAW,
-            AV_CODEC_ID_PCM_MULAW };
-
-    std::vector<unsigned> av_list;
-    av_list.reserve(list.size());
-    for (auto& item : list) {
-        if (item > 0 and item < CODEC_ID_MAPPING.size())
-            av_list.emplace_back(CODEC_ID_MAPPING[item]);
-    }
-    return av_list;
-}
-
 std::string
 Account::mapStateNumberToString(RegistrationState state)
 {
@@ -490,41 +269,6 @@ Account::getDefaultCodecDetails(const unsigned& codecId)
     return {};
 }
 
-#define find_iter() \
-    const auto& iter = details.find(key); \
-    if (iter == details.end()) { \
-        JAMI_ERR("Couldn't find key \"%s\"", key); \
-        return; \
-    }
-
-void
-Account::parseString(const std::map<std::string, std::string>& details,
-                     const char* key,
-                     std::string& s)
-{
-    find_iter();
-    s = iter->second;
-}
-
-void
-Account::parsePath(const std::map<std::string, std::string>& details,
-                   const char* key,
-                   std::string& s,
-                   const std::string& base)
-{
-    find_iter();
-    s = fileutils::getCleanPath(base, iter->second);
-}
-
-void
-Account::parseBool(const std::map<std::string, std::string>& details, const char* key, bool& b)
-{
-    find_iter();
-    b = iter->second == TRUE_STR;
-}
-
-#undef find_iter
-
 /**
  * Get the UPnP IP (external router) address.
  * If use UPnP is set to false, the address will be empty.
@@ -670,36 +414,25 @@ Account::getActiveAccountCodecInfoList(MediaType mediaType) const
 const std::string&
 Account::getUserAgentName()
 {
-    if (hasCustomUserAgent_ and not customUserAgent_.empty())
-        return customUserAgent_;
-    return DEFAULT_USER_AGENT;
+    return config_->customUserAgent.empty() ? DEFAULT_USER_AGENT : config_->customUserAgent;
 }
 
 std::string
-Account::setDefaultUserAgent()
+Account::getDefaultUserAgent()
 {
-    // Build the default user-agent string
-    std::string defaultUA;
-    defaultUA.append(PACKAGE_NAME);
-    defaultUA.append(" ");
-    defaultUA.append(libjami::version());
-    defaultUA.append(" (");
-    defaultUA.append(libjami::platform());
-    defaultUA.append(")");
-
-    return defaultUA;
+    return fmt::format("{:s} {:s} ({:s})", PACKAGE_NAME, libjami::version(), libjami::platform());
 }
 
 void
 Account::addDefaultModerator(const std::string& uri)
 {
-    defaultModerators_.insert(uri);
+    config_->defaultModerators.insert(uri);
 }
 
 void
 Account::removeDefaultModerator(const std::string& uri)
 {
-    defaultModerators_.erase(uri);
+    config_->defaultModerators.erase(uri);
 }
 
 bool
diff --git a/src/account.h b/src/account.h
index 0ce987713e48b17c47e64a30f09bb72e4efb4825..0de5e06c27c118caf189da39569ddb8e43effd80 100644
--- a/src/account.h
+++ b/src/account.h
@@ -19,9 +19,7 @@
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
  */
-
-#ifndef ACCOUNT_H
-#define ACCOUNT_H
+#pragma once
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -39,6 +37,7 @@
 #include "logger.h"
 #include "compiler_intrinsics.h" // include the "UNUSED" macro
 #include "call_set.h"
+#include "account_config.h"
 
 #include <functional>
 #include <string>
@@ -52,11 +51,6 @@
 #include <mutex>
 #include <chrono>
 
-namespace YAML {
-class Emitter;
-class Node;
-} // namespace YAML
-
 namespace Json {
 class Value;
 }
@@ -88,7 +82,7 @@ public:
  * It contains account, configuration, VoIP Link and Calls (inside the VoIPLink)
  */
 
-class Account : public Serializable, public std::enable_shared_from_this<Account>
+class Account: public std::enable_shared_from_this<Account>
 {
 public:
     Account(const std::string& accountID);
@@ -104,21 +98,49 @@ public:
      */
     void hangupCalls();
 
-    virtual void setAccountDetails(const std::map<std::string, std::string>& details);
-
-    virtual std::map<std::string, std::string> getAccountDetails() const;
+    virtual std::unique_ptr<AccountConfig> buildConfig() const = 0;
 
-    virtual std::map<std::string, std::string> getVolatileAccountDetails() const;
-
-    virtual std::string getFromUri() const = 0;
+    void setConfig(std::unique_ptr<AccountConfig>&& config) {
+        std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
+        config_ = std::move(config);
+        loadConfig();
+    }
 
     /**
-     * Load the settings for this account.
+     * Load the settings in this account.
      */
-    virtual void loadConfig() = 0;
+    virtual void loadConfig();
+
+    const AccountConfig& config() const {
+        if (config_) return *config_;
+        else throw std::runtime_error("Account doesn't have a configuration");
+    }
+
+    inline void editConfig(std::function<void(AccountConfig& config)>&& edit) {
+        std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
+        edit(*config_);
+        saveConfig();
+    }
 
-    virtual void serialize(YAML::Emitter& out) const;
-    virtual void unserialize(const YAML::Node& node);
+    virtual void saveConfig() const;
+
+    void setAccountDetails(const std::map<std::string, std::string>& details) {
+        std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
+        if (not config_)
+            config_ = buildConfig();
+        config_->fromMap(details);
+        loadConfig();
+        saveConfig();
+    }
+
+    std::map<std::string, std::string> getAccountDetails() const {
+        std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
+        return config().toMap();
+    }
+
+    virtual std::map<std::string, std::string> getVolatileAccountDetails() const;
+
+    virtual std::string getFromUri() const = 0;
 
     /**
      * Get the account ID
@@ -197,21 +219,25 @@ public:
 
     virtual void setPushNotificationToken(const std::string& pushDeviceToken = "")
     {
-        deviceKey_ = pushDeviceToken;
+        editConfig([&](AccountConfig& config){
+            config.deviceKey = pushDeviceToken;
+        });
     }
 
     virtual void setPushNotificationTopic(const std::string& topic = "")
     {
-        notificationTopic_ = topic;
+        editConfig([&](AccountConfig& config){
+            config.notificationTopic = topic;
+        });
     }
 
     /**
      * Tell if the account is enable or not.
      * @return true if enabled, false otherwise
      */
-    bool isEnabled() const noexcept { return enabled_; }
+    bool isEnabled() const { return config().enabled; }
 
-    void setEnabled(bool enable) noexcept { enabled_ = enable; }
+    void setEnabled(bool enable) { config_->enabled = enable; }
 
     /**
      * Tell if the account is activated
@@ -221,10 +247,14 @@ public:
 
     void setActive(bool active) noexcept { active_ = active; }
 
-    bool isUsable() const noexcept { return enabled_ and active_; }
+    bool isUsable() const { return config().enabled and active_; }
 
-    void enableVideo(bool enable) { videoEnabled_ = enable; }
-    bool isVideoEnabled() const noexcept { return videoEnabled_; }
+    void enableVideo(bool enable) {
+        editConfig([&](AccountConfig& config){
+            config.videoEnabled = enable;
+        });
+    }
+    bool isVideoEnabled() const { return config().videoEnabled; }
 
     /**
      * Set the registration state of the specified link
@@ -234,15 +264,9 @@ public:
                                       unsigned detail_code = 0,
                                       const std::string& detail_str = {});
 
-    /* They should be treated like macro definitions by the C++ compiler */
-    const std::string& getUsername() const { return username_; }
-
-    const std::string& getHostname() const { return hostname_; }
-    void setHostname(const std::string& hostname) { hostname_ = hostname; }
-
-    const std::string& getAlias() const { return alias_; }
-
-    void setAlias(const std::string& alias) { alias_ = alias; }
+    const std::string& getUsername() const { return config().username; }
+    const std::string& getHostname() const { return config().hostname; }
+    const std::string& getAlias() const { return config().alias; }
 
     static std::vector<unsigned> getDefaultCodecsId();
     static std::map<std::string, std::string> getDefaultCodecDetails(const unsigned& codecId);
@@ -264,29 +288,13 @@ public:
     std::shared_ptr<AccountCodecInfo> searchCodecByPayload(unsigned payload, MediaType mediaType);
 
     std::string getRingtonePath() const { return ringtonePath_; }
+    bool getRingtoneEnabled() const { return config().ringtoneEnabled; }
+    std::string getDisplayName() const { return config().displayName; }
+    std::string getMailBox() const { return config().mailbox; }
 
-    void setRingtonePath(const std::string& path) { ringtonePath_ = path; }
-
-    bool getRingtoneEnabled() const { return ringtoneEnabled_; }
-    void setRingtoneEnabled(bool enable) { ringtoneEnabled_ = enable; }
-
-    std::string getDisplayName() const { return displayName_; }
-    void setDisplayName(const std::string& name) { displayName_ = name; }
-
-    std::string getMailBox() const { return mailBox_; }
-
-    void setMailBox(const std::string& mb) { mailBox_ = mb; }
-
-    bool isRendezVous() const { return isRendezVous_; }
-
-    bool isAutoAnswerEnabled() const { return autoAnswerEnabled_; }
-
-    bool isReadReceiptEnabled() const { return sendReadReceipt_; }
-
-    static const char* const VIDEO_CODEC_ENABLED;
-    static const char* const VIDEO_CODEC_NAME;
-    static const char* const VIDEO_CODEC_PARAMETERS;
-    static const char* const VIDEO_CODEC_BITRATE;
+    bool isRendezVous() const { return config().isRendezVous; }
+    bool isAutoAnswerEnabled() const { return config().autoAnswerEnabled; }
+    bool isReadReceiptEnabled() const { return config().sendReadReceipt; }
 
     /**
      * returns whether or not UPnP is enabled and active
@@ -326,33 +334,25 @@ public:
 
     void setCodecInactive(unsigned codecId);
 
-    std::vector<unsigned> convertIdToAVId(const std::vector<unsigned>& list);
-
     /**
      * Get the user-agent
      */
     const std::string& getUserAgentName();
 
-    std::set<std::string> getDefaultModerators() const { return defaultModerators_; }
+    std::set<std::string> getDefaultModerators() const { return config().defaultModerators; }
 
     void addDefaultModerator(const std::string& peerURI);
     void removeDefaultModerator(const std::string& peerURI);
 
-    bool isLocalModeratorsEnabled() const { return localModeratorsEnabled_; }
-    void enableLocalModerators(bool isModEnabled) { localModeratorsEnabled_ = isModEnabled; }
-    bool isAllModerators() const { return allModeratorsEnabled_; }
-    void setAllModerators(bool isAllModeratorEnabled)
-    {
-        allModeratorsEnabled_ = isAllModeratorEnabled;
-    }
+    bool isLocalModeratorsEnabled() const { return config().localModeratorsEnabled; }
+    bool isAllModerators() const { return config().allModeratorsEnabled; }
 
     // Enable/disable ICE for media
     bool isIceForMediaEnabled() const { return iceForMediaEnabled_; }
     void enableIceForMedia(bool enable) { iceForMediaEnabled_ = enable; }
 
     // Enable/disable generation of empty offers
-    bool isEmptyOffersEnabled() const { return emptyOffersEnabled_; }
-    void enableEmptyOffers(bool enable) { emptyOffersEnabled_ = enable; }
+    bool isEmptyOffersEnabled() const { return false; }
 
     // Check if a Daemon version (typically peer's version) satisfies the
     // minimum required version. This check is typically used to disable a
@@ -416,64 +416,10 @@ private:
 protected:
     void updateUpnpController();
 
-    static void parseString(const std::map<std::string, std::string>& details,
-                            const char* key,
-                            std::string& s);
-    static void parseBool(const std::map<std::string, std::string>& details,
-                          const char* key,
-                          bool& b);
-    static void parsePath(const std::map<std::string, std::string>& details,
-                          const char* key,
-                          std::string& s,
-                          const std::string& base);
-
-    template<class T>
-    static inline void parseInt(const std::map<std::string, std::string>& details,
-                                const char* key,
-                                T& i)
-    {
-        const auto& iter = details.find(key);
-        if (iter == details.end()) {
-            JAMI_ERR("Couldn't find key \"%s\"", key);
-            return;
-        }
-        i = atoi(iter->second.c_str());
-    }
+    std::unique_ptr<AccountConfig> config_ {};
 
     friend class ConfigurationTest;
 
-    // General configuration keys for accounts
-    static const char* const ALL_CODECS_KEY;
-    static const char* const RINGTONE_PATH_KEY;
-    static const char* const RINGTONE_ENABLED_KEY;
-    static const char* const VIDEO_ENABLED_KEY;
-    static const char* const DISPLAY_NAME_KEY;
-    static const char* const ALIAS_KEY;
-    static const char* const TYPE_KEY;
-    static const char* const ID_KEY;
-    static const char* const USERNAME_KEY;
-    static const char* const AUTHENTICATION_USERNAME_KEY;
-    static const char* const PASSWORD_KEY;
-    static const char* const HOSTNAME_KEY;
-    static const char* const ACCOUNT_ENABLE_KEY;
-    static const char* const ACCOUNT_AUTOANSWER_KEY;
-    static const char* const ACCOUNT_READRECEIPT_KEY;
-    static const char* const ACCOUNT_ISRENDEZVOUS_KEY;
-    static const char* const ACCOUNT_ACTIVE_CALL_LIMIT_KEY;
-    static const char* const MAILBOX_KEY;
-    static const char* const USER_AGENT_KEY;
-    static const char* const HAS_CUSTOM_USER_AGENT_KEY;
-    static const char* const PRESENCE_MODULE_ENABLED_KEY;
-    static const char* const UPNP_ENABLED_KEY;
-    static const char* const PROXY_ENABLED_KEY;
-    static const char* const PROXY_SERVER_KEY;
-    static const char* const PROXY_PUSH_TOKEN_KEY;
-    static const char* const PROXY_PUSH_IOS_TOPIC_KEY;
-    static const char* const ACTIVE_CODEC_KEY;
-    static const char* const DEFAULT_MODERATORS_KEY;
-    static const char* const LOCAL_MODERATORS_ENABLED_KEY;
-    static const char* const ALL_MODERATORS_ENABLED_KEY;
-
     static const std::string DEFAULT_USER_AGENT;
 
     static std::string mapStateNumberToString(RegistrationState state);
@@ -481,7 +427,7 @@ protected:
     /**
      * Build the user-agent string
      */
-    static std::string setDefaultUserAgent();
+    static std::string getDefaultUserAgent();
 
     /**
      * Account ID are assign in constructor and shall not changed
@@ -490,28 +436,6 @@ protected:
 
     mutable std::recursive_mutex configurationMutex_ {};
 
-    /**
-     * Account login information: username
-     */
-    std::string username_;
-
-    /**
-     * Account login information: hostname
-     */
-    std::string hostname_;
-
-    /**
-     * Account login information: Alias
-     */
-    std::string alias_;
-
-    /**
-     * Tells if the account is enabled.
-     * This implies the link will be initialized on startup.
-     * Modified by the configuration (key: ENABLED)
-     */
-    bool enabled_;
-
     /**
      * Tells if the account is active now.
      * This allows doRegister to be called.
@@ -519,24 +443,6 @@ protected:
      */
     bool active_ {true};
 
-    /* If true, automatically answer calls to this account */
-    bool autoAnswerEnabled_;
-
-    // If true, send Displayed status (and emit to the client)
-    bool sendReadReceipt_;
-
-    /* If true mix calls into a conference */
-    bool isRendezVous_;
-
-    /**
-     * The number of concurrent calls for the account
-     * -1: Unlimited
-     *  0: Do not disturb
-     *  1: Single call
-     *  +: Multi line
-     */
-    int activeCallLimit_ {-1};
-
     /*
      * The general, protocol neutral registration
      * state of the account
@@ -557,66 +463,15 @@ protected:
      */
     std::string ringtonePath_;
 
-    /**
-     * Allows user to temporarily disable video calling
-     */
-
-    bool videoEnabled_ = true;
-
-    /**
-     * Play ringtone when receiving a call
-     */
-    bool ringtoneEnabled_;
-
-    /**
-     * Display name when calling
-     */
-    std::string displayName_;
-
-    /**
-     * User-agent used for registration
-     */
-    std::string customUserAgent_;
-
-    //  true if user has overridden default
-    bool hasCustomUserAgent_;
-
-    /**
-     * Account mail box
-     */
-    std::string mailBox_;
-
     /**
      * UPnP IGD controller and the mutex to access it
      */
-    bool upnpEnabled_;
     mutable std::mutex upnp_mtx {};
     std::shared_ptr<jami::upnp::Controller> upnpCtrl_;
 
-    std::set<std::string> defaultModerators_ {};
-    bool localModeratorsEnabled_;
-    bool allModeratorsEnabled_;
-
-    bool multiStreamEnabled_ {false};
     bool iceForMediaEnabled_ {true};
     bool iceCompIdRfc5245Compliant_ {false};
 
-    /**
-     * Allow generating empty offers. Mainly to validate proper
-     * handling of incoming empty offers.
-     */
-    bool emptyOffersEnabled_ {false};
-
-    /**
-     * Device push notification token.
-     */
-    std::string deviceKey_ {};
-
-    /**
-     * Push notification topic.
-     */
-    std::string notificationTopic_ {};
-
     /**
      * private account codec searching functions
      */
@@ -636,4 +491,3 @@ operator<<(std::ostream& os, const Account& acc)
 
 } // namespace jami
 
-#endif
diff --git a/src/account_config.cpp b/src/account_config.cpp
index 5fee1015d4665f7024311e8aef3d7f32ec2d730a..ef0144bb80ceab0f93f7dcc5eed9fbb24d81ee33 100644
--- a/src/account_config.cpp
+++ b/src/account_config.cpp
@@ -17,7 +17,7 @@
 #include "account_config.h"
 #include "account_const.h"
 #include "account_schema.h"
-#include "yamlparser.h"
+#include "config/yamlparser.h"
 #include "string_utils.h"
 #include "fileutils.h"
 
@@ -171,7 +171,7 @@ parsePath(const std::map<std::string, std::string>& details,
 {
     auto it = details.find(key);
     if (it != details.end())
-        s = fileutils::getCleanPath(base, it->second);
+        s = fileutils::getFullPath(base, it->second);
 }
 
 }
diff --git a/src/account_factory.cpp b/src/account_factory.cpp
index 8093512ce27e787fcce0e32c3cd02a9c17d29ae8..2a298cb24ec9a9662f0566e7af6a09c70f55b391 100644
--- a/src/account_factory.cpp
+++ b/src/account_factory.cpp
@@ -35,14 +35,12 @@ const char* const AccountFactory::DEFAULT_ACCOUNT_TYPE = SIPAccount::ACCOUNT_TYP
 
 AccountFactory::AccountFactory()
 {
-    auto sipfunc = [](const std::string& id) {
+    generators_.emplace(SIPAccount::ACCOUNT_TYPE, [](const std::string& id) {
         return std::make_shared<SIPAccount>(id, true);
-    };
-    generators_.emplace(SIPAccount::ACCOUNT_TYPE, sipfunc);
-    auto dhtfunc = [](const std::string& id) {
-        return std::make_shared<JamiAccount>(id, false);
-    };
-    generators_.emplace(JamiAccount::ACCOUNT_TYPE, dhtfunc);
+    });
+    generators_.emplace(JamiAccount::ACCOUNT_TYPE, [](const std::string& id) {
+        return std::make_shared<JamiAccount>(id);
+    });
 }
 
 std::shared_ptr<Account>
@@ -66,7 +64,7 @@ AccountFactory::createAccount(const char* const accountType, const std::string&
 }
 
 bool
-AccountFactory::isSupportedType(const char* const name) const
+AccountFactory::isSupportedType(std::string_view name) const
 {
     return generators_.find(name) != generators_.cend();
 }
@@ -74,14 +72,15 @@ AccountFactory::isSupportedType(const char* const name) const
 void
 AccountFactory::removeAccount(Account& account)
 {
-    const auto* account_type = account.getAccountType();
-
+    std::string_view account_type = account.getAccountType();
     std::lock_guard<std::recursive_mutex> lock(mutex_);
     const auto& id = account.getAccountID();
     JAMI_DEBUG("Removing account {:s}", id);
-    auto& map = accountMaps_.at(account_type);
-    map.erase(id);
-    JAMI_DEBUG("Remaining {:d} {:s} account(s)", map.size(), account_type);
+    auto m = accountMaps_.find(account_type);
+    if (m != accountMaps_.end()) {
+        m->second.erase(id);
+        JAMI_DEBUG("Remaining {:d} {:s} account(s)", m->second.size(), account_type);
+    }
 }
 
 void
diff --git a/src/account_factory.h b/src/account_factory.h
index 540a7614f96c63b1735f73ff5ac1192001b13628..ebd5a1b5dfd177a1a414ba5044af169aef49bc77 100644
--- a/src/account_factory.h
+++ b/src/account_factory.h
@@ -44,7 +44,7 @@ public:
 
     AccountFactory();
 
-    bool isSupportedType(const char* const accountType) const;
+    bool isSupportedType(std::string_view accountType) const;
 
     std::shared_ptr<Account> createAccount(const char* const accountType, const std::string& id);
 
diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp
index 18aa5202f251a9d76b505fc5700a34e16420348d..d7bfaf16f89ce9c8ddab73262cecc43421ba7aee 100644
--- a/src/client/configurationmanager.cpp
+++ b/src/client/configurationmanager.cpp
@@ -37,6 +37,8 @@
 #include "connectivity/ip_utils.h"
 #include "sip/sipaccount.h"
 #include "jamidht/jamiaccount.h"
+#include "sip/sipaccount_config.h"
+#include "jamidht/jamiaccount_config.h"
 #include "audio/audiolayer.h"
 #include "system_codec_container.h"
 #include "account_const.h"
@@ -347,10 +349,7 @@ changeAccountPassword(const std::string& accountID,
                       const std::string& password_new)
 {
     if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountID))
-        if (acc->changeArchivePassword(password_old, password_new)) {
-            jami::Manager::instance().saveConfig(acc);
-            return true;
-        }
+        return acc->changeArchivePassword(password_old, password_new);
     return false;
 }
 
@@ -425,9 +424,9 @@ std::map<std::string, std::string>
 getAccountTemplate(const std::string& accountType)
 {
     if (accountType == Account::ProtocolNames::RING)
-        return jami::JamiAccount("dummy", false).getAccountDetails();
+        return jami::JamiAccountConfig().toMap();
     else if (accountType == Account::ProtocolNames::SIP)
-        return jami::SIPAccount("dummy", false).getAccountDetails();
+        return jami::SipAccountConfig().toMap();
     return {};
 }
 
@@ -923,10 +922,14 @@ setCredentials(const std::string& accountID,
 {
     if (auto sipaccount = jami::Manager::instance().getAccount<SIPAccount>(accountID)) {
         sipaccount->doUnregister([&](bool /* transport_free */) {
-            sipaccount->setCredentials(details);
+            sipaccount->editConfig([&](jami::SipAccountConfig& config){
+                config.setCredentials(details);
+            });
+            sipaccount->loadConfig();
             if (sipaccount->isEnabled())
                 sipaccount->doRegister();
         });
+        jami::Manager::instance().saveConfig(sipaccount);
     }
 }
 
diff --git a/src/config/yamlparser.cpp b/src/config/yamlparser.cpp
index 8b8a398e7547c769cc587395cf9832469b4a6c60..94baf956808a327a2ad2aa9825225b0036601f50 100644
--- a/src/config/yamlparser.cpp
+++ b/src/config/yamlparser.cpp
@@ -30,21 +30,28 @@ parsePath(const YAML::Node& node, const char* key, std::string& path, const std:
 {
     std::string val;
     parseValue(node, key, val);
-    path = fileutils::getCleanPath(base, val);
+    path = fileutils::getFullPath(base, val);
+}
+
+void
+parsePathOptional(const YAML::Node& node, const char* key, std::string& path, const std::string& base)
+{
+    std::string val;
+    if (parseValueOptional(node, key, val))
+        path = fileutils::getFullPath(base, val);
 }
 
-// FIXME: Maybe think of something more clever, this is due to yaml-cpp's poor
-// handling of empty values for nested collections.
 std::vector<std::map<std::string, std::string>>
 parseVectorMap(const YAML::Node& node, const std::initializer_list<std::string>& keys)
 {
     std::vector<std::map<std::string, std::string>> result;
+    result.reserve(node.size());
     for (const auto& n : node) {
         std::map<std::string, std::string> t;
         for (const auto& k : keys) {
             t[k] = n[k].as<std::string>("");
         }
-        result.push_back(t);
+        result.emplace_back(std::move(t));
     }
     return result;
 }
@@ -54,9 +61,7 @@ parseVector(const YAML::Node& node)
 {
     std::set<std::string> result;
     for (const auto& n : node) {
-        std::string t;
-        t = n.as<std::string>("");
-        result.emplace(t);
+        result.emplace(n.as<std::string>(""));
     }
     return result;
 }
diff --git a/src/config/yamlparser.h b/src/config/yamlparser.h
index e63a7e6b02852eb28758e9d1dbc1f50b3e2af88a..adc53020908a90d4e9997623e42c6a823510073a 100644
--- a/src/config/yamlparser.h
+++ b/src/config/yamlparser.h
@@ -50,6 +50,7 @@ parseValueOptional(const YAML::Node& node, const char* key, T& value)
 }
 
 void parsePath(const YAML::Node& node, const char* key, std::string& path, const std::string& base);
+void parsePathOptional(const YAML::Node& node, const char* key, std::string& path, const std::string& base);
 
 std::vector<std::map<std::string, std::string>> parseVectorMap(
     const YAML::Node& node, const std::initializer_list<std::string>& keys);
diff --git a/src/connectivity/sip_utils.h b/src/connectivity/sip_utils.h
index 17891ba51e06903c3594326c49018ddd040c1bd6..b633c5332c63a25d47a10ed76aa9870920b763c1 100644
--- a/src/connectivity/sip_utils.h
+++ b/src/connectivity/sip_utils.h
@@ -39,16 +39,18 @@
 namespace jami {
 namespace sip_utils {
 
+using namespace std::literals;
+
 // SIP methods. Only list methods that need to be explicitly
 // handled
 
 namespace SIP_METHODS {
-constexpr std::string_view MESSAGE = "MESSAGE";
-constexpr std::string_view INFO = "INFO";
-constexpr std::string_view OPTIONS = "OPTIONS";
-constexpr std::string_view PUBLISH = "PUBLISH";
-constexpr std::string_view REFER = "REFER";
-constexpr std::string_view NOTIFY = "NOTIFY";
+constexpr std::string_view MESSAGE = "MESSAGE"sv;
+constexpr std::string_view INFO = "INFO"sv;
+constexpr std::string_view OPTIONS = "OPTIONS"sv;
+constexpr std::string_view PUBLISH = "PUBLISH"sv;
+constexpr std::string_view REFER = "REFER"sv;
+constexpr std::string_view NOTIFY = "NOTIFY"sv;
 } // namespace SIP_METHODS
 
 static constexpr int DEFAULT_SIP_PORT {5060};
@@ -87,9 +89,9 @@ getKeyExchangeName(KeyExchangeProtocol kx)
 }
 
 static inline KeyExchangeProtocol
-getKeyExchangeProtocol(const char* name)
+getKeyExchangeProtocol(std::string_view name)
 {
-    return !std::strcmp("sdes", name) ? KeyExchangeProtocol::SDES : KeyExchangeProtocol::NONE;
+    return name == "sdes"sv ? KeyExchangeProtocol::SDES : KeyExchangeProtocol::NONE;
 }
 
 /**
diff --git a/src/jamidht/CMakeLists.txt b/src/jamidht/CMakeLists.txt
index 1c08d51cf3efdeb04b5097380946d5d609b1b601..a929bb867ed8700315afd290b2a709d2602cfcbe 100644
--- a/src/jamidht/CMakeLists.txt
+++ b/src/jamidht/CMakeLists.txt
@@ -28,6 +28,8 @@ list (APPEND Source_Files__jamidht
       "${CMAKE_CURRENT_SOURCE_DIR}/jami_contact.h"
       "${CMAKE_CURRENT_SOURCE_DIR}/jamiaccount.cpp"
       "${CMAKE_CURRENT_SOURCE_DIR}/jamiaccount.h"
+      "${CMAKE_CURRENT_SOURCE_DIR}/jamiaccount_config.cpp"
+      "${CMAKE_CURRENT_SOURCE_DIR}/jamiaccount_config.h"
       "${CMAKE_CURRENT_SOURCE_DIR}/channel_handler.h"
       "${CMAKE_CURRENT_SOURCE_DIR}/conversation_channel_handler.h"
       "${CMAKE_CURRENT_SOURCE_DIR}/conversation_channel_handler.cpp"
diff --git a/src/jamidht/Makefile.am b/src/jamidht/Makefile.am
index bd64a47812f6960c4a801800a335f2d1cacefb96..3a626939cdc7ae8204b3a8b6db05f3397a326424 100644
--- a/src/jamidht/Makefile.am
+++ b/src/jamidht/Makefile.am
@@ -8,6 +8,8 @@ libjamiacc_la_SOURCES = \
 	./jamidht/abstract_sip_transport.h \
 	./jamidht/jamiaccount.cpp \
 	./jamidht/jamiaccount.h \
+	./jamidht/jamiaccount_config.cpp \
+	./jamidht/jamiaccount_config.h \
 	./jamidht/channeled_transport.h \
 	./jamidht/channeled_transport.cpp \
 	./jamidht/channeled_transfers.h \
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index d7da1f3325620f747d177ec8e7625b6106854382..f617bd91266cf7e1fd8252d4ac62fefc4b3505e4 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -1,16 +1,10 @@
 /*
  *  Copyright (C) 2004-2022 Savoir-faire Linux Inc.
  *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
- *  Author: Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
- *  Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>
- *  Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
- *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
+ *  the Free Software Foundation; either version 3, or (at your option)
+ *  any later version.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -18,8 +12,7 @@
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ *  along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #ifdef HAVE_CONFIG_H
@@ -76,7 +69,6 @@
 #include "data_transfer.h"
 #include "conversation.h"
 
-#include "config/yamlparser.h"
 #include "connectivity/security/certstore.h"
 #include "libdevcrypto/Common.h"
 #include "base64.h"
@@ -226,18 +218,11 @@ struct JamiAccount::DiscoveredPeer
 
 static constexpr const char* const RING_URI_PREFIX = "ring:";
 static constexpr const char* const JAMI_URI_PREFIX = "jami:";
-static constexpr const char* DEFAULT_TURN_SERVER = "turn.jami.net";
-static constexpr const char* DEFAULT_TURN_USERNAME = "ring";
-static constexpr const char* DEFAULT_TURN_PWD = "ring";
-static constexpr const char* DEFAULT_TURN_REALM = "ring";
 static const auto PROXY_REGEX = std::regex(
     "(https?://)?([\\w\\.\\-_\\~]+)(:(\\d+)|:\\[(.+)-(.+)\\])?");
 static const std::string PEER_DISCOVERY_JAMI_SERVICE = "jami";
 const constexpr auto PEER_DISCOVERY_EXPIRATION = std::chrono::minutes(1);
 
-constexpr const char* const JamiAccount::ACCOUNT_TYPE;
-constexpr const std::pair<uint16_t, uint16_t> JamiAccount::DHT_PORT_RANGE {4000, 8888};
-
 using ValueIdDist = std::uniform_int_distribution<dht::Value::Id>;
 
 static std::string_view
@@ -297,26 +282,16 @@ JamiAccount::createIceTransport(const Args&... args)
     return ice;
 }
 
-JamiAccount::JamiAccount(const std::string& accountID, bool /* presenceEnabled */)
-    : SIPAccountBase(accountID)
+JamiAccount::JamiAccount(const std::string& accountId)
+    : SIPAccountBase(accountId)
     , dht_(new dht::DhtRunner)
-    , idPath_(fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID())
-    , cachePath_(fileutils::get_cache_dir() + DIR_SEPARATOR_STR + getAccountID())
+    , idPath_(fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId)
+    , cachePath_(fileutils::get_cache_dir() + DIR_SEPARATOR_STR + accountId)
     , dataPath_(cachePath_ + DIR_SEPARATOR_STR "values")
     , dhtPeerConnector_ {}
     , connectionManager_ {}
+    , nonSwarmTransferManager_(std::make_shared<TransferManager>(accountId, ""))
 {
-    // Force the SFL turn server if none provided yet
-    turnServer_ = DEFAULT_TURN_SERVER;
-    turnServerUserName_ = DEFAULT_TURN_USERNAME;
-    turnServerPwd_ = DEFAULT_TURN_PWD;
-    turnServerRealm_ = DEFAULT_TURN_REALM;
-    turnEnabled_ = true;
-
-    proxyListUrl_ = DHT_DEFAULT_PROXY_LIST_URL;
-    proxyServer_ = DHT_DEFAULT_PROXY;
-    nonSwarmTransferManager_ = std::make_shared<TransferManager>(getAccountID(), "");
-
     try {
         std::istringstream is(fileutils::loadCacheTextFile(cachePath_ + DIR_SEPARATOR_STR "dhtproxy",
                                                            std::chrono::hours(24 * 7)));
@@ -326,16 +301,10 @@ JamiAccount::JamiAccount(const std::string& accountID, bool /* presenceEnabled *
                  getAccountID().c_str(),
                  e.what());
     }
-
-    setActiveCodecs({});
 }
 
 JamiAccount::~JamiAccount() noexcept
 {
-    if (peerDiscovery_) {
-        peerDiscovery_->stopPublish(PEER_DISCOVERY_JAMI_SERVICE);
-        peerDiscovery_->stopDiscovery(PEER_DISCOVERY_JAMI_SERVICE);
-    }
     if (auto dht = dht_)
         dht->join();
 }
@@ -462,7 +431,7 @@ JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, std::st
     } catch (...) {
 #if HAVE_RINGNS
         NameDirectory::lookupUri(suffix,
-                                 nameServer_,
+                                 config().nameServer,
                                  [wthis_ = weak(), call](const std::string& result,
                                                          NameDirectory::Response response) {
                                      // we may run inside an unknown thread, but following code must
@@ -813,149 +782,23 @@ JamiAccount::saveConfig() const
 {
     try {
         YAML::Emitter accountOut;
-        serialize(accountOut);
-        auto accountConfig = getPath() + DIR_SEPARATOR_STR + "config.yml";
-
+        config().serialize(accountOut);
+        auto accountConfig = config().path + DIR_SEPARATOR_STR + "config.yml";
         std::lock_guard<std::mutex> lock(fileutils::getFileLock(accountConfig));
         std::ofstream fout = fileutils::ofstream(accountConfig);
-        fout << accountOut.c_str();
-        JAMI_DBG("Exported account to %s", accountConfig.c_str());
+        fout.write(accountOut.c_str(), accountOut.size());
+        JAMI_DBG("Saved account config to %s", accountConfig.c_str());
     } catch (const std::exception& e) {
-        JAMI_ERR("Error exporting account: %s", e.what());
+        JAMI_ERR("Error saving account config: %s", e.what());
     }
 }
 
 void
-JamiAccount::serialize(YAML::Emitter& out) const
+JamiAccount::loadConfig()
 {
-    std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
-
-    if (registrationState_ == RegistrationState::INITIALIZING)
-        return;
-
-    out << YAML::BeginMap;
-    SIPAccountBase::serialize(out);
-    out << YAML::Key << Conf::DHT_PORT_KEY << YAML::Value << dhtDefaultPort_;
-    out << YAML::Key << Conf::DHT_PUBLIC_IN_CALLS << YAML::Value << dhtPublicInCalls_;
-    out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_HISTORY << YAML::Value << allowPeersFromHistory_;
-    out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_CONTACT << YAML::Value << allowPeersFromContact_;
-    out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_TRUSTED << YAML::Value << allowPeersFromTrusted_;
-    out << YAML::Key << libjami::Account::ConfProperties::DHT_PEER_DISCOVERY << YAML::Value
-        << dhtPeerDiscovery_;
-    out << YAML::Key << libjami::Account::ConfProperties::ACCOUNT_PEER_DISCOVERY << YAML::Value
-        << accountPeerDiscovery_;
-    out << YAML::Key << libjami::Account::ConfProperties::ACCOUNT_PUBLISH << YAML::Value
-        << accountPublish_;
-
-    out << YAML::Key << Conf::PROXY_ENABLED_KEY << YAML::Value << proxyEnabled_;
-    out << YAML::Key << Conf::PROXY_SERVER_KEY << YAML::Value << proxyServer_;
-    out << YAML::Key << libjami::Account::ConfProperties::DHT_PROXY_LIST_URL << YAML::Value
-        << proxyListUrl_;
-
-#if HAVE_RINGNS
-    out << YAML::Key << libjami::Account::ConfProperties::RingNS::URI << YAML::Value << nameServer_;
-    if (not registeredName_.empty())
-        out << YAML::Key << libjami::Account::VolatileProperties::REGISTERED_NAME << YAML::Value
-            << registeredName_;
-#endif
-
-    out << YAML::Key << libjami::Account::ConfProperties::ARCHIVE_PATH << YAML::Value << archivePath_;
-    out << YAML::Key << libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD << YAML::Value
-        << archiveHasPassword_;
-    out << YAML::Key << Conf::RING_ACCOUNT_RECEIPT << YAML::Value << receipt_;
-    if (receiptSignature_.size() > 0)
-        out << YAML::Key << Conf::RING_ACCOUNT_RECEIPT_SIG << YAML::Value
-            << YAML::Binary(receiptSignature_.data(), receiptSignature_.size());
-    out << YAML::Key << libjami::Account::ConfProperties::DEVICE_NAME << YAML::Value << deviceName_;
-    out << YAML::Key << libjami::Account::ConfProperties::MANAGER_URI << YAML::Value << managerUri_;
-    out << YAML::Key << libjami::Account::ConfProperties::MANAGER_USERNAME << YAML::Value
-        << managerUsername_;
-
-    // tls submap
-    out << YAML::Key << Conf::TLS_KEY << YAML::Value << YAML::BeginMap;
-    SIPAccountBase::serializeTls(out);
-    out << YAML::EndMap;
-
-    out << YAML::EndMap;
-}
-
-void
-JamiAccount::unserialize(const YAML::Node& node)
-{
-    std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
-
-    using yaml_utils::parseValue;
-    using yaml_utils::parseValueOptional;
-    using yaml_utils::parsePath;
-
-    SIPAccountBase::unserialize(node);
-
-    // get tls submap
-    const auto& tlsMap = node[Conf::TLS_KEY];
-    parsePath(tlsMap, Conf::CERTIFICATE_KEY, tlsCertificateFile_, idPath_);
-    parsePath(tlsMap, Conf::CALIST_KEY, tlsCaListFile_, idPath_);
-    parseValue(tlsMap, Conf::TLS_PASSWORD_KEY, tlsPassword_);
-    parsePath(tlsMap, Conf::PRIVATE_KEY_KEY, tlsPrivateKeyFile_, idPath_);
-
-    parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_HISTORY, allowPeersFromHistory_);
-    parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_CONTACT, allowPeersFromContact_);
-    parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_TRUSTED, allowPeersFromTrusted_);
-
-    parseValue(node, Conf::PROXY_ENABLED_KEY, proxyEnabled_);
-    parseValue(node, Conf::PROXY_SERVER_KEY, proxyServer_);
-    try {
-        parseValue(node, libjami::Account::ConfProperties::DHT_PROXY_LIST_URL, proxyListUrl_);
-    } catch (const std::exception& e) {
-        proxyListUrl_ = DHT_DEFAULT_PROXY_LIST_URL;
-    }
-
-    parseValueOptional(node, libjami::Account::ConfProperties::DEVICE_NAME, deviceName_);
-    parseValueOptional(node, libjami::Account::ConfProperties::MANAGER_URI, managerUri_);
-    parseValueOptional(node, libjami::Account::ConfProperties::MANAGER_USERNAME, managerUsername_);
-
-    try {
-        parsePath(node, libjami::Account::ConfProperties::ARCHIVE_PATH, archivePath_, idPath_);
-        parseValue(node, libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD, archiveHasPassword_);
-    } catch (const std::exception& e) {
-        JAMI_WARN("can't read archive path: %s", e.what());
-        archiveHasPassword_ = true;
-    }
-
-    try {
-        parseValue(node, Conf::RING_ACCOUNT_RECEIPT, receipt_);
-        auto receipt_sig = node[Conf::RING_ACCOUNT_RECEIPT_SIG].as<YAML::Binary>();
-        receiptSignature_ = {receipt_sig.data(), receipt_sig.data() + receipt_sig.size()};
-    } catch (const std::exception& e) {
-        JAMI_WARN("can't read receipt: %s", e.what());
-    }
-
-    // HACK
-    // MacOS doesn't seems to close the DHT port sometimes, so re-using the DHT port seems
-    // to make the DHT unusable (Address already in use, and SO_REUSEADDR & SO_REUSEPORT
-    // doesn't seems to work). For now, use a random port
-    // See https://git.jami.net/savoirfairelinux/ring-client-macosx/issues/221
-    // TODO: parseValueOptional(node, Conf::DHT_PORT_KEY, dhtDefaultPort_);
-    if (not dhtDefaultPort_)
-        dhtDefaultPort_ = getRandomEvenPort(DHT_PORT_RANGE);
-
-    parseValueOptional(node, libjami::Account::ConfProperties::DHT_PEER_DISCOVERY, dhtPeerDiscovery_);
-    parseValueOptional(node,
-                       libjami::Account::ConfProperties::ACCOUNT_PEER_DISCOVERY,
-                       accountPeerDiscovery_);
-    parseValueOptional(node, libjami::Account::ConfProperties::ACCOUNT_PUBLISH, accountPublish_);
-
-#if HAVE_RINGNS
-    parseValueOptional(node, libjami::Account::ConfProperties::RingNS::URI, nameServer_);
-    if (registeredName_.empty()) {
-        parseValueOptional(node,
-                           libjami::Account::VolatileProperties::REGISTERED_NAME,
-                           registeredName_);
-    }
-#endif
-
-    parseValue(node, Conf::DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_);
-
-    loadAccount();
+    SIPAccountBase::loadConfig();
+    registeredName_ = config().registeredName;
+    loadAccount(config().archive_password, config().archive_pin, config().archive_path);
 }
 
 bool
@@ -966,13 +809,17 @@ JamiAccount::changeArchivePassword(const std::string& password_old, const std::s
             JAMI_ERR("[Account %s] Can't change archive password", getAccountID().c_str());
             return false;
         }
-        archiveHasPassword_ = not password_new.empty();
+        editConfig([&](JamiAccountConfig& config){
+            config.archiveHasPassword = not password_new.empty();
+        });
     } catch (const std::exception& ex) {
         JAMI_ERR("[Account %s] Can't change archive password: %s",
                  getAccountID().c_str(),
                  ex.what());
         if (password_old.empty()) {
-            archiveHasPassword_ = true;
+            editConfig([&](JamiAccountConfig& config){
+                config.archiveHasPassword = true;
+            });
             emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(),
                                                                           getAccountDetails());
         }
@@ -1037,8 +884,10 @@ JamiAccount::setValidity(const std::string& pwd, const dht::InfoHash& id, int64_
 void
 JamiAccount::forceReloadAccount()
 {
-    receipt_.clear();
-    receiptSignature_.clear();
+    editConfig([&](JamiAccountConfig& conf){
+        conf.receipt.clear();
+        conf.receiptSignature.clear();
+    });
     loadAccount();
 }
 
@@ -1200,42 +1049,44 @@ JamiAccount::loadAccount(const std::string& archive_password,
             }
         }};
 
+    const auto& conf = config();
     try {
         auto onAsync = [w = weak()](AccountManager::AsyncUser&& cb) {
             if (auto this_ = w.lock())
                 cb(*this_->accountManager_);
         };
-        if (managerUri_.empty()) {
+        if (conf.managerUri.empty()) {
             accountManager_.reset(new ArchiveAccountManager(
                 getPath(),
                 onAsync,
                 [this]() { return getAccountDetails(); },
-                archivePath_.empty() ? "archive.gz" : archivePath_,
-                nameServer_));
+                conf.archivePath.empty() ? "archive.gz" : conf.archivePath,
+                conf.nameServer));
         } else {
             accountManager_.reset(
-                new ServerAccountManager(getPath(), onAsync, managerUri_, nameServer_));
+                new ServerAccountManager(getPath(), onAsync, conf.managerUri, conf.nameServer));
         }
 
-        auto id = accountManager_->loadIdentity(tlsCertificateFile_,
-                                                tlsPrivateKeyFile_,
-                                                tlsPassword_);
+        auto id = accountManager_->loadIdentity(conf.tlsCertificateFile,
+                                                conf.tlsPrivateKeyFile,
+                                                conf.tlsPassword);
 
         if (auto info = accountManager_->useIdentity(id,
-                                                     receipt_,
-                                                     receiptSignature_,
-                                                     managerUsername_,
+                                                     conf.receipt,
+                                                     conf.receiptSignature,
+                                                     conf.managerUsername,
                                                      callbacks)) {
             // normal loading path
             id_ = std::move(id);
-            username_ = info->accountId;
+            config_->username = info->accountId;
             JAMI_WARN("[Account %s] loaded account identity", getAccountID().c_str());
             if (not isEnabled()) {
                 setRegistrationState(RegistrationState::UNREGISTERED);
             }
             convModule()->loadConversations();
         } else if (isEnabled()) {
-            if (not managerUri_.empty() and archive_password.empty()) {
+            JAMI_WARNING("[Account {}] useIdentity failed!", getAccountID());
+            if (not conf.managerUri.empty() and archive_password.empty()) {
                 Migration::setState(accountID_, Migration::State::INVALID);
                 setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
                 return;
@@ -1250,12 +1101,9 @@ JamiAccount::loadAccount(const std::string& archive_password,
                                   });
 
             std::unique_ptr<AccountManager::AccountCredentials> creds;
-            if (managerUri_.empty()) {
+            if (conf.managerUri.empty()) {
                 auto acreds = std::make_unique<ArchiveAccountManager::ArchiveAccountCredentials>();
-                if (archivePath_.empty()) {
-                    archivePath_ = "archive.gz";
-                }
-                auto archivePath = fileutils::getFullPath(idPath_, archivePath_);
+                auto archivePath = fileutils::getFullPath(idPath_, conf.archivePath);
                 bool hasArchive = fileutils::isFile(archivePath);
 
                 if (not archive_path.empty()) {
@@ -1278,17 +1126,17 @@ JamiAccount::loadAccount(const std::string& archive_password,
                 creds = std::move(acreds);
             } else {
                 auto screds = std::make_unique<ServerAccountManager::ServerAccountCredentials>();
-                screds->username = managerUsername_;
+                screds->username = conf.managerUsername;
                 creds = std::move(screds);
             }
             creds->password = archive_password;
-            archiveHasPassword_ = !archive_password.empty();
+            bool hasPassword = !archive_password.empty();
 
             accountManager_->initAuthentication(
                 fDeviceKey,
                 ip_utils::getDeviceName(),
                 std::move(creds),
-                [this, migrating](const AccountInfo& info,
+                [this, migrating, hasPassword](const AccountInfo& info,
                                   const std::map<std::string, std::string>& config,
                                   std::string&& receipt,
                                   std::vector<uint8_t>&& receipt_signature) {
@@ -1296,45 +1144,41 @@ JamiAccount::loadAccount(const std::string& archive_password,
 
                     fileutils::check_dir(idPath_.c_str(), 0700);
 
-                    // save the chain including CA
                     auto id = info.identity;
-                    std::tie(tlsPrivateKeyFile_, tlsCertificateFile_) = saveIdentity(id,
-                                                                                     idPath_,
-                                                                                     DEVICE_ID_PATH);
-                    id_ = std::move(id);
-                    tlsPassword_ = {};
-
-                    username_ = info.accountId;
-                    registeredName_ = managerUsername_;
-                    deviceName_ = accountManager_->getAccountDeviceName();
-
-                    auto nameServerIt = config.find(libjami::Account::ConfProperties::RingNS::URI);
-                    if (nameServerIt != config.end() && !nameServerIt->second.empty()) {
-                        nameServer_ = nameServerIt->second;
-                    }
-                    auto displayNameIt = config.find(libjami::Account::ConfProperties::DISPLAYNAME);
-                    if (displayNameIt != config.end() && !displayNameIt->second.empty()) {
-                        displayName_ = displayNameIt->second;
-                    }
+                    editConfig([&](JamiAccountConfig& conf){
+                        std::tie(conf.tlsPrivateKeyFile, conf.tlsCertificateFile) = saveIdentity(id,
+                                                                                        idPath_,
+                                                                                        DEVICE_ID_PATH);
+                        conf.tlsPassword = {};
+                        conf.archiveHasPassword = hasPassword;
+                        if (not conf.managerUri.empty()) {
+                            conf.registeredName = conf.managerUsername;
+                            registeredName_ = conf.managerUsername;
+                        }
+                        conf.username = info.accountId;
+                        conf.deviceName = accountManager_->getAccountDeviceName();
 
-                    receipt_ = std::move(receipt);
-                    receiptSignature_ = std::move(receipt_signature);
+                        auto nameServerIt = config.find(libjami::Account::ConfProperties::RingNS::URI);
+                        if (nameServerIt != config.end() && !nameServerIt->second.empty()) {
+                            conf.nameServer = nameServerIt->second;
+                        }
+                        auto displayNameIt = config.find(libjami::Account::ConfProperties::DISPLAYNAME);
+                        if (displayNameIt != config.end() && !displayNameIt->second.empty()) {
+                            conf.displayName = displayNameIt->second;
+                        }
+                        conf.receipt = std::move(receipt);
+                        conf.receiptSignature = std::move(receipt_signature);
+                        conf.fromMap(config);
+                    });
+                    id_ = std::move(id);
                     if (migrating) {
                         Migration::setState(getAccountID(), Migration::State::SUCCESS);
                     }
-
-                    // Use the provided config by JAMS instead of default one
-                    auto details = getAccountDetails();
-                    for (const auto& [key, value] : config)
-                        details[key] = value;
-                    setAccountDetails(details);
-
-                    if (not info.photo.empty() or not displayName_.empty())
+                    if (not info.photo.empty() or not config_->displayName.empty())
                         emitSignal<libjami::ConfigurationSignal::AccountProfileReceived>(getAccountID(),
-                                                                                       displayName_,
+                                                                                       config_->displayName,
                                                                                        info.photo);
                     setRegistrationState(RegistrationState::UNREGISTERED);
-                    saveConfig();
                     convModule()->loadConversations();
                     doRegister();
                 },
@@ -1371,149 +1215,6 @@ JamiAccount::loadAccount(const std::string& archive_password,
     }
 }
 
-void
-JamiAccount::setAccountDetails(const std::map<std::string, std::string>& details)
-{
-    std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
-    SIPAccountBase::setAccountDetails(details);
-
-    // TLS
-    parsePath(details, Conf::CONFIG_TLS_CA_LIST_FILE, tlsCaListFile_, idPath_);
-    parsePath(details, Conf::CONFIG_TLS_CERTIFICATE_FILE, tlsCertificateFile_, idPath_);
-    parsePath(details, Conf::CONFIG_TLS_PRIVATE_KEY_FILE, tlsPrivateKeyFile_, idPath_);
-    parseString(details, Conf::CONFIG_TLS_PASSWORD, tlsPassword_);
-
-    if (hostname_.empty())
-        hostname_ = DHT_DEFAULT_BOOTSTRAP;
-    parseString(details, libjami::Account::ConfProperties::BOOTSTRAP_LIST_URL, bootstrapListUrl_);
-    parseInt(details, Conf::CONFIG_DHT_PORT, dhtDefaultPort_);
-    parseBool(details, Conf::CONFIG_DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_);
-    parseBool(details, libjami::Account::ConfProperties::DHT_PEER_DISCOVERY, dhtPeerDiscovery_);
-    parseBool(details,
-              libjami::Account::ConfProperties::ACCOUNT_PEER_DISCOVERY,
-              accountPeerDiscovery_);
-    parseBool(details, libjami::Account::ConfProperties::ACCOUNT_PUBLISH, accountPublish_);
-    parseBool(details,
-              libjami::Account::ConfProperties::ALLOW_CERT_FROM_HISTORY,
-              allowPeersFromHistory_);
-    parseBool(details,
-              libjami::Account::ConfProperties::ALLOW_CERT_FROM_CONTACT,
-              allowPeersFromContact_);
-    parseBool(details,
-              libjami::Account::ConfProperties::ALLOW_CERT_FROM_TRUSTED,
-              allowPeersFromTrusted_);
-    if (not dhtDefaultPort_)
-        dhtDefaultPort_ = getRandomEvenPort(DHT_PORT_RANGE);
-
-    parseString(details, libjami::Account::ConfProperties::MANAGER_URI, managerUri_);
-    parseString(details, libjami::Account::ConfProperties::MANAGER_USERNAME, managerUsername_);
-    parseString(details, libjami::Account::ConfProperties::USERNAME, username_);
-
-    std::string archive_password;
-    std::string archive_pin;
-    std::string archive_path;
-    parseString(details, libjami::Account::ConfProperties::ARCHIVE_PASSWORD, archive_password);
-    parseString(details, libjami::Account::ConfProperties::ARCHIVE_PIN, archive_pin);
-    std::transform(archive_pin.begin(), archive_pin.end(), archive_pin.begin(), ::toupper);
-    parsePath(details, libjami::Account::ConfProperties::ARCHIVE_PATH, archive_path, idPath_);
-    parseString(details, libjami::Account::ConfProperties::DEVICE_NAME, deviceName_);
-
-    auto oldProxyServer = proxyServer_, oldProxyServerList = proxyListUrl_;
-    parseString(details, libjami::Account::ConfProperties::DHT_PROXY_LIST_URL, proxyListUrl_);
-    parseBool(details, libjami::Account::ConfProperties::PROXY_ENABLED, proxyEnabled_);
-    parseString(details, libjami::Account::ConfProperties::PROXY_SERVER, proxyServer_);
-    // Migrate from old versions
-    if (proxyServer_.empty()
-        || ((proxyServer_ == "dhtproxy.jami.net" || proxyServer_ == "dhtproxy.ring.cx")
-            && proxyServerCached_.empty()))
-        proxyServer_ = DHT_DEFAULT_PROXY;
-    if (proxyServer_ != oldProxyServer || oldProxyServerList != proxyListUrl_) {
-        JAMI_DBG("DHT Proxy configuration changed, resetting cache");
-        proxyServerCached_ = {};
-        auto proxyCachePath = cachePath_ + DIR_SEPARATOR_STR "dhtproxy";
-        auto proxyListCachePath = cachePath_ + DIR_SEPARATOR_STR "dhtproxylist";
-        std::remove(proxyCachePath.c_str());
-        std::remove(proxyListCachePath.c_str());
-    }
-    if (not managerUri_.empty() and managerUri_.rfind("http", 0) != 0) {
-        managerUri_ = "https://" + managerUri_;
-    }
-
-#if HAVE_RINGNS
-    parseString(details, libjami::Account::ConfProperties::RingNS::URI, nameServer_);
-#endif
-
-    // update device name if necessary
-    if (accountManager_)
-        accountManager_->setAccountDeviceName(deviceName_);
-
-    loadAccount(archive_password, archive_pin, archive_path);
-}
-
-std::map<std::string, std::string>
-JamiAccount::getAccountDetails() const
-{
-    std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
-    std::map<std::string, std::string> a = SIPAccountBase::getAccountDetails();
-    a.emplace(Conf::CONFIG_DHT_PORT, std::to_string(dhtDefaultPort_));
-    a.emplace(Conf::CONFIG_DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_ ? TRUE_STR : FALSE_STR);
-    a.emplace(libjami::Account::ConfProperties::DHT_PEER_DISCOVERY,
-              dhtPeerDiscovery_ ? TRUE_STR : FALSE_STR);
-    a.emplace(libjami::Account::ConfProperties::ACCOUNT_PEER_DISCOVERY,
-              accountPeerDiscovery_ ? TRUE_STR : FALSE_STR);
-    a.emplace(libjami::Account::ConfProperties::ACCOUNT_PUBLISH,
-              accountPublish_ ? TRUE_STR : FALSE_STR);
-    if (accountManager_) {
-        if (auto info = accountManager_->getInfo()) {
-            a.emplace(libjami::Account::ConfProperties::DEVICE_ID, info->deviceId);
-            a.emplace(libjami::Account::ConfProperties::RingNS::ACCOUNT, info->ethAccount);
-        }
-    }
-    a.emplace(libjami::Account::ConfProperties::DEVICE_NAME, deviceName_);
-    a.emplace(libjami::Account::ConfProperties::Presence::SUPPORT_SUBSCRIBE, TRUE_STR);
-    if (not archivePath_.empty() or not managerUri_.empty())
-        a.emplace(libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD,
-                  archiveHasPassword_ ? TRUE_STR : FALSE_STR);
-
-    /* these settings cannot be changed (read only), but clients should still be
-     * able to read what they are */
-    a.emplace(Conf::CONFIG_SRTP_KEY_EXCHANGE, sip_utils::getKeyExchangeName(getSrtpKeyExchange()));
-    a.emplace(Conf::CONFIG_SRTP_ENABLE, isSrtpEnabled() ? TRUE_STR : FALSE_STR);
-    a.emplace(Conf::CONFIG_SRTP_RTP_FALLBACK, getSrtpFallback() ? TRUE_STR : FALSE_STR);
-
-    a.emplace(Conf::CONFIG_TLS_CA_LIST_FILE, fileutils::getFullPath(idPath_, tlsCaListFile_));
-    a.emplace(Conf::CONFIG_TLS_CERTIFICATE_FILE,
-              fileutils::getFullPath(idPath_, tlsCertificateFile_));
-    a.emplace(Conf::CONFIG_TLS_PRIVATE_KEY_FILE,
-              fileutils::getFullPath(idPath_, tlsPrivateKeyFile_));
-    a.emplace(Conf::CONFIG_TLS_PASSWORD, tlsPassword_);
-    a.emplace(Conf::CONFIG_TLS_METHOD, "Automatic");
-    a.emplace(Conf::CONFIG_TLS_CIPHERS, "");
-    a.emplace(Conf::CONFIG_TLS_SERVER_NAME, "");
-    a.emplace(Conf::CONFIG_TLS_VERIFY_SERVER, TRUE_STR);
-    a.emplace(Conf::CONFIG_TLS_VERIFY_CLIENT, TRUE_STR);
-    a.emplace(Conf::CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, TRUE_STR);
-    a.emplace(Conf::CONFIG_TLS_DISABLE_SECURE_DLG_CHECK, TRUE_STR);
-    a.emplace(libjami::Account::ConfProperties::ALLOW_CERT_FROM_HISTORY,
-              allowPeersFromHistory_ ? TRUE_STR : FALSE_STR);
-    a.emplace(libjami::Account::ConfProperties::ALLOW_CERT_FROM_CONTACT,
-              allowPeersFromContact_ ? TRUE_STR : FALSE_STR);
-    a.emplace(libjami::Account::ConfProperties::ALLOW_CERT_FROM_TRUSTED,
-              allowPeersFromTrusted_ ? TRUE_STR : FALSE_STR);
-    /* GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT is defined as -1 */
-    a.emplace(Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, "-1");
-    a.emplace(libjami::Account::ConfProperties::PROXY_ENABLED, proxyEnabled_ ? TRUE_STR : FALSE_STR);
-    a.emplace(libjami::Account::ConfProperties::PROXY_SERVER, proxyServer_);
-    a.emplace(libjami::Account::ConfProperties::DHT_PROXY_LIST_URL, proxyListUrl_);
-    a.emplace(libjami::Account::ConfProperties::MANAGER_URI, managerUri_);
-    a.emplace(libjami::Account::ConfProperties::MANAGER_USERNAME, managerUsername_);
-#if HAVE_RINGNS
-    a.emplace(libjami::Account::ConfProperties::RingNS::URI, nameServer_);
-#endif
-
-    return a;
-}
-
 std::map<std::string, std::string>
 JamiAccount::getVolatileAccountDetails() const
 {
@@ -1537,7 +1238,7 @@ JamiAccount::lookupName(const std::string& name)
     std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
     if (accountManager_)
         accountManager_->lookupUri(name,
-                                   nameServer_,
+                                   config().nameServer,
                                    [acc = getAccountID(), name](const std::string& result,
                                                                 NameDirectory::Response response) {
                                        emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(
@@ -1583,7 +1284,9 @@ JamiAccount::registerName(const std::string& password, const std::string& name)
                 if (response == NameDirectory::RegistrationResponse::success) {
                     if (auto this_ = w.lock()) {
                         this_->registeredName_ = name;
-                        this_->saveConfig();
+                        this_->editConfig([&](JamiAccountConfig& config){
+                            config.registeredName = name;
+                        });
                         emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
                             this_->accountID_, this_->getVolatileAccountDetails());
                     }
@@ -1758,13 +1461,11 @@ std::vector<std::string>
 JamiAccount::loadBootstrap() const
 {
     std::vector<std::string> bootstrap;
-    if (!hostname_.empty()) {
-        std::string_view stream(hostname_), node_addr;
-        while (jami::getline(stream, node_addr, ';'))
-            bootstrap.emplace_back(node_addr);
-        for (const auto& b : bootstrap)
-            JAMI_DBG("[Account %s] Bootstrap node: %s", getAccountID().c_str(), b.c_str());
-    }
+    std::string_view stream(config().hostname), node_addr;
+    while (jami::getline(stream, node_addr, ';'))
+        bootstrap.emplace_back(node_addr);
+    for (const auto& b : bootstrap)
+        JAMI_DBG("[Account %s] Bootstrap node: %s", getAccountID().c_str(), b.c_str());
     return bootstrap;
 }
 
@@ -1923,6 +1624,7 @@ JamiAccount::doRegister_()
     }
 
     JAMI_DBG("[Account %s] Starting account...", getAccountID().c_str());
+    const auto& conf = config();
 
     try {
         if (not accountManager_ or not accountManager_->getInfo())
@@ -1938,7 +1640,7 @@ JamiAccount::doRegister_()
         convModule()->clearPendingFetch();
 
 #if HAVE_RINGNS
-        // Look for registered name on the blockchain
+        // Look for registered name
         accountManager_->lookupAddress(
             accountManager_->getInfo()->accountId,
             [w = weak()](const std::string& result, const NameDirectory::Response& response) {
@@ -1946,12 +1648,18 @@ JamiAccount::doRegister_()
                     if (response == NameDirectory::Response::found) {
                         if (this_->registeredName_ != result) {
                             this_->registeredName_ = result;
+                            this_->editConfig([&](JamiAccountConfig& config){
+                                config.registeredName = result;
+                            });
                             emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
                                 this_->accountID_, this_->getVolatileAccountDetails());
                         }
                     } else if (response == NameDirectory::Response::notFound) {
                         if (not this_->registeredName_.empty()) {
                             this_->registeredName_.clear();
+                            this_->editConfig([&](JamiAccountConfig& config){
+                                config.registeredName.clear();
+                            });
                             emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
                                 this_->accountID_, this_->getVolatileAccountDetails());
                         }
@@ -1967,12 +1675,12 @@ JamiAccount::doRegister_()
         config.dht_config.id = id_;
         config.dht_config.cert_cache_all = true;
         config.push_node_id = getAccountID();
-        config.push_token = deviceKey_;
-        config.push_topic = notificationTopic_;
+        config.push_token = conf.deviceKey;
+        config.push_topic = conf.notificationTopic;
         config.threaded = true;
-        config.peer_discovery = dhtPeerDiscovery_;
-        config.peer_publish = dhtPeerDiscovery_;
-        if (proxyEnabled_)
+        config.peer_discovery = conf.dhtPeerDiscovery;
+        config.peer_publish = conf.dhtPeerDiscovery;
+        if (conf.proxyEnabled)
             config.proxy_server = proxyServerCached_;
 
         if (not config.proxy_server.empty()) {
@@ -1985,13 +1693,13 @@ JamiAccount::doRegister_()
         }
 
         // check if dht peer service is enabled
-        if (accountPeerDiscovery_ or accountPublish_) {
+        if (conf.accountPeerDiscovery or conf.accountPublish) {
             peerDiscovery_ = std::make_shared<dht::PeerDiscovery>();
-            if (accountPeerDiscovery_) {
+            if (conf.accountPeerDiscovery) {
                 JAMI_INFO("[Account %s] starting Jami account discovery...", getAccountID().c_str());
                 startAccountDiscovery();
             }
-            if (accountPublish_)
+            if (conf.accountPublish)
                 startAccountPublish();
         }
         dht::DhtRunner::Context context {};
@@ -2142,7 +1850,7 @@ JamiAccount::doRegister_()
                           getAccountID().c_str(),
                           name.c_str());
 
-                if (turnEnabled_ && !cacheTurnV4_) {
+                if (this->config().turnEnabled && !cacheTurnV4_) {
                     // If TURN is enabled, but no TURN cached, there can be a temporary resolution
                     // error to solve. Sometimes, a connectivity change is not enough, so even if
                     // this case is really rare, it should be easy to avoid.
@@ -2442,6 +2150,11 @@ JamiAccount::doUnregister(std::function<void(bool)> released_cb)
     std::condition_variable cv;
     bool shutdown_complete {false};
 
+    if (peerDiscovery_) {
+        peerDiscovery_->stopPublish(PEER_DISCOVERY_JAMI_SERVICE);
+        peerDiscovery_->stopDiscovery(PEER_DISCOVERY_JAMI_SERVICE);
+    }
+
     JAMI_WARN("[Account %s] unregistering account %p", getAccountID().c_str(), this);
     dht_->shutdown(
         [&] {
@@ -2741,14 +2454,15 @@ JamiAccount::loadCachedUrl(const std::string& url,
 void
 JamiAccount::loadCachedProxyServer(std::function<void(const std::string& proxy)> cb)
 {
-    if (proxyEnabled_ and proxyServerCached_.empty()) {
-        JAMI_DBG("[Account %s] loading DHT proxy URL: %s",
-                 getAccountID().c_str(),
-                 proxyListUrl_.c_str());
-        if (proxyListUrl_.empty()) {
-            cb(getDhtProxyServer(proxyServer_));
+    const auto& conf = config();
+    if (conf.proxyEnabled and proxyServerCached_.empty()) {
+        JAMI_DEBUG("[Account {:s}] loading DHT proxy URL: {:s}",
+                 getAccountID(),
+                 conf.proxyListUrl);
+        if (conf.proxyListUrl.empty()) {
+            cb(getDhtProxyServer(conf.proxyServer));
         } else {
-            loadCachedUrl(proxyListUrl_,
+            loadCachedUrl(conf.proxyListUrl,
                           cachePath_ + DIR_SEPARATOR_STR "dhtproxylist",
                           std::chrono::hours(24 * 3),
                           [w = weak(), cb = std::move(cb)](const dht::http::Response& response) {
@@ -2756,7 +2470,7 @@ JamiAccount::loadCachedProxyServer(std::function<void(const std::string& proxy)>
                                   if (response.status_code == 200) {
                                       cb(sthis->getDhtProxyServer(response.body));
                                   } else {
-                                      cb(sthis->getDhtProxyServer(sthis->proxyServer_));
+                                      cb(sthis->getDhtProxyServer(sthis->config().proxyServer));
                                   }
                               }
                           });
@@ -2840,8 +2554,8 @@ std::string
 JamiAccount::getFromUri() const
 {
     const std::string uri = "<sip:" + accountManager_->getInfo()->accountId + "@ring.dht>";
-    if (not displayName_.empty())
-        return "\"" + displayName_ + "\" " + uri;
+    if (not config().displayName.empty())
+        return "\"" + config().displayName + "\" " + uri;
     return uri;
 }
 
@@ -2953,7 +2667,7 @@ JamiAccount::getContactHeader(const std::shared_ptr<SipTransport>& sipTransport)
         auto address = td->self->getLocalAddress().toString(true);
         bool reliable = transport->flag & PJSIP_TRANSPORT_RELIABLE;
         return fmt::format("\"{}\" <sips:{}{}{};transport={}>",
-                           displayName_,
+                           config().displayName,
                            id_.second->getId().toString(),
                            address.empty() ? "" : "@",
                            address,
@@ -2961,7 +2675,7 @@ JamiAccount::getContactHeader(const std::shared_ptr<SipTransport>& sipTransport)
     } else {
         JAMI_ERR("getContactHeader: no SIP transport provided");
         return fmt::format("\"{}\" <sips:{}@ring.dht>",
-                           displayName_,
+                           config().displayName,
                            id_.second->getId().toString());
     }
 }
@@ -3354,8 +3068,7 @@ JamiAccount::sendMessage(const std::string& to,
             std::unique_lock<std::mutex> l(confirm->lock);
             if (not confirm->replied) {
                 if (auto this_ = w.lock()) {
-                    JAMI_DBG() << "[Account " << this_->getAccountID() << "] [message " << token
-                               << "] Timeout";
+                    JAMI_DBG() << "[Account " << this_->getAccountID() << "] [message " << token << "] Timeout";
                     for (auto& t : confirm->listenTokens)
                         this_->dht_->cancelListen(t.first, std::move(t.second));
                     confirm->listenTokens.clear();
@@ -3499,16 +3212,18 @@ JamiAccount::closePeerConnection(const libjami::DataTransferId& tid)
 void
 JamiAccount::setPushNotificationToken(const std::string& token)
 {
-    JAMI_WARN("[Account %s] setPushNotificationToken: %s", getAccountID().c_str(), token.c_str());
-    deviceKey_ = token;
-    dht_->setPushNotificationToken(deviceKey_);
+    JAMI_WARNING("[Account {:s}] setPushNotificationToken: {:s}", getAccountID(), token);
+    SIPAccountBase::setPushNotificationToken(token);
+    if (dht_)
+        dht_->setPushNotificationToken(token);
 }
 
 void
 JamiAccount::setPushNotificationTopic(const std::string& topic)
 {
-    notificationTopic_ = topic;
-    dht_->setPushNotificationTopic(notificationTopic_);
+    SIPAccountBase::setPushNotificationTopic(topic);
+    if (dht_)
+        dht_->setPushNotificationTopic(topic);
 }
 
 /**
@@ -3529,7 +3244,7 @@ JamiAccount::getUserUri() const
     if (not registeredName_.empty())
         return JAMI_URI_PREFIX + registeredName_;
 #endif
-    return JAMI_URI_PREFIX + username_;
+    return JAMI_URI_PREFIX + config().username;
 }
 
 std::vector<libjami::Message>
@@ -3543,7 +3258,7 @@ JamiAccount::startAccountPublish()
 {
     AccountPeerInfo info_pub;
     info_pub.accountId = dht::InfoHash(accountManager_->getInfo()->accountId);
-    info_pub.displayName = displayName_;
+    info_pub.displayName = config().displayName;
     peerDiscovery_->startPublish<AccountPeerInfo>(PEER_DISCOVERY_JAMI_SERVICE, info_pub);
 }
 
@@ -3609,6 +3324,7 @@ JamiAccount::setActiveCodecs(const std::vector<unsigned>& list)
         setCodecActive(AV_CODEC_ID_H264);
         setCodecActive(AV_CODEC_ID_VP8);
     }
+    config_->activeCodecs = getActiveCodecs(MEDIA_ALL);
 }
 
 // Member management
@@ -3758,7 +3474,8 @@ JamiAccount::cacheTurnServers()
         // Avoid multiple refresh
         if (this_->isRefreshing_.exchange(true))
             return;
-        if (!this_->turnEnabled_) {
+        const auto& conf = this_->config();
+        if (!conf.turnEnabled) {
             // In this case, we do not use any TURN server
             std::lock_guard<std::mutex> lk(this_->cachedTurnMutex_);
             this_->cacheTurnV4_.reset();
@@ -3771,7 +3488,7 @@ JamiAccount::cacheTurnServers()
         // Retrieve old cached value if available.
         // This means that we directly get the correct value when launching the application on the
         // same network
-        std::string server = this_->turnServer_.empty() ? DEFAULT_TURN_SERVER : this_->turnServer_;
+        std::string server = conf.turnServer.empty() ? DEFAULT_TURN_SERVER : conf.turnServer;
         // No need to resolve, it's already a valid address
         if (IpAddr::isValid(server, AF_INET)) {
             this_->cacheTurnV4_ = std::make_unique<IpAddr>(server, AF_INET);
@@ -3813,7 +3530,7 @@ JamiAccount::cacheTurnServers()
             // Update TURN
             this_->cacheTurnV4_ = std::make_unique<IpAddr>(std::move(turnV4));
         }
-        auto turnV6 = IpAddr {server.empty() ? DEFAULT_TURN_SERVER : server, AF_INET6};
+        auto turnV6 = IpAddr {server, AF_INET6};
         {
             if (turnV6) {
                 // Cache value to avoid a delay when starting up Jami
@@ -3913,7 +3630,7 @@ JamiAccount::requestSIPConnection(const std::string& peerId,
     connectionManager_->connectDevice(
         deviceId,
         "sip",
-        [w = weak(), id, pc = std::move(pc)](std::shared_ptr<ChannelSocket> socket,
+        [w = weak(), id = std::move(id), pc = std::move(pc)](std::shared_ptr<ChannelSocket> socket,
                                              const DeviceId&) {
             if (socket)
                 return;
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index 5469355a0e0648d370672d9fdeed4aae24f8cf73..b6be21108592faf3a24d3db2b07406753aa9bb0a 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -35,6 +35,7 @@
 #include "connectivity/multiplexed_socket.h"
 #include "data_transfer.h"
 #include "uri.h"
+#include "jamiaccount_config.h"
 
 #include "noncopyable.h"
 #include "connectivity/ip_utils.h"
@@ -67,11 +68,6 @@
 #include "namedirectory.h"
 #endif
 
-namespace YAML {
-class Node;
-class Emitter;
-} // namespace YAML
-
 namespace dev {
 template<unsigned N>
 class FixedHash;
@@ -104,16 +100,8 @@ using GitSocketList = std::map<DeviceId,                               /* device
 class JamiAccount : public SIPAccountBase
 {
 public:
-    constexpr static const char* const ACCOUNT_TYPE = "RING";
-    constexpr static const in_port_t DHT_DEFAULT_PORT = 4222;
-    constexpr static const char* const DHT_DEFAULT_BOOTSTRAP = "bootstrap.jami.net";
-    constexpr static const char* const DHT_DEFAULT_PROXY = "dhtproxy.jami.net:[80-95]";
-    constexpr static const char* const DHT_DEFAULT_BOOTSTRAP_LIST_URL
-        = "https://config.jami.net/boostrapList";
-    constexpr static const char* const DHT_DEFAULT_PROXY_LIST_URL
-        = "https://config.jami.net/proxyList";
-
-    /* constexpr */ static const std::pair<uint16_t, uint16_t> DHT_PORT_RANGE;
+    constexpr static auto ACCOUNT_TYPE = ACCOUNT_TYPE_JAMI;
+    constexpr static const std::pair<uint16_t, uint16_t> DHT_PORT_RANGE {4000, 8888};
     constexpr static int ICE_STREAMS_COUNT {1};
     constexpr static int ICE_COMP_COUNT_PER_STREAM {1};
 
@@ -138,43 +126,29 @@ public:
 
     const std::string& getPath() const { return idPath_; }
 
+    const JamiAccountConfig& config() const {
+        return *static_cast<const JamiAccountConfig*>(&Account::config());
+    }
+
+    void loadConfig() override;
+
     /**
      * Constructor
      * @param accountID The account identifier
      */
-    JamiAccount(const std::string& accountID, bool presenceEnabled);
+    JamiAccount(const std::string& accountId);
 
     ~JamiAccount() noexcept;
 
-    /**
-     * Serialize internal state of this account for configuration
-     * @param YamlEmitter the configuration engine which generate the configuration file
-     */
-    virtual void serialize(YAML::Emitter& out) const override;
-
-    /**
-     * Populate the internal state for this account based on info stored in the configuration file
-     * @param The configuration node for this account
-     */
-    virtual void unserialize(const YAML::Node& node) override;
-
-    /**
-     * Return an map containing the internal state of this account. Client application can use this
-     * method to manage account info.
-     * @return A map containing the account information.
-     */
-    virtual std::map<std::string, std::string> getAccountDetails() const override;
-
     /**
      * Retrieve volatile details such as recent registration errors
      * @return std::map< std::string, std::string > The account volatile details
      */
     virtual std::map<std::string, std::string> getVolatileAccountDetails() const override;
 
-    /**
-     * Actually useless, since config loading is done in init()
-     */
-    void loadConfig() override {}
+    std::unique_ptr<AccountConfig> buildConfig() const override {
+        return std::make_unique<JamiAccountConfig>(getAccountID(), idPath_);
+    }
 
     /**
      * Adds an account id to the list of accounts to track on the DHT for
@@ -285,9 +259,7 @@ public:
                        const std::map<std::string, std::string>& payloads) override;
 
     virtual bool isTlsEnabled() const override { return true; }
-
     bool isSrtpEnabled() const override { return true; }
-    KeyExchangeProtocol getSrtpKeyExchange() const { return KeyExchangeProtocol::SDES; }
 
     virtual bool getSrtpFallback() const override { return false; }
 
@@ -446,7 +418,13 @@ public:
      */
     void startAccountDiscovery();
 
-    void saveConfig() const;
+    void saveConfig() const override;
+
+    inline void editConfig(std::function<void(JamiAccountConfig& conf)>&& edit) {
+        Account::editConfig([&](AccountConfig& conf) {
+            edit(*static_cast<JamiAccountConfig*>(&conf));
+        });
+    }
 
     /**
      * Get current discovered peers account id and display name
@@ -660,13 +638,6 @@ private:
                                  const std::string& to_id,
                                  IpAddr target);
 
-    /**
-     * Set the internal state for this account, mainly used to manage account details from the
-     * client application.
-     * @param The map containing the account information.
-     */
-    virtual void setAccountDetails(const std::map<std::string, std::string>& details) override;
-
     /**
      * Start a SIP Call
      * @param call  The current call
@@ -741,7 +712,6 @@ private:
     void updateContactHeader();
 
 #if HAVE_RINGNS
-    std::string nameServer_;
     std::string registeredName_;
 #endif
     std::shared_ptr<dht::Logger> logger_;
@@ -754,17 +724,10 @@ private:
     std::map<dht::Value::Id, PendingMessage> sentMessages_;
     std::set<std::string, std::less<>> treatedMessages_ {};
 
-    std::string deviceName_ {};
     std::string idPath_ {};
     std::string cachePath_ {};
     std::string dataPath_ {};
 
-    std::string archivePath_ {};
-    bool archiveHasPassword_ {true};
-
-    std::string receipt_ {};
-    std::vector<uint8_t> receiptSignature_ {};
-
     /* tracked buddies presence */
     mutable std::mutex buddyInfoMtx;
     std::map<dht::InfoHash, BuddyInfo> trackedBuddies_;
@@ -793,27 +756,15 @@ private:
     /* Current UPNP mapping */
     upnp::Mapping dhtUpnpMapping_ {upnp::PortType::UDP};
 
-    bool dhtPeerDiscovery_ {false};
-
     /**
      * Proxy
      */
-    std::string proxyListUrl_;
-    bool proxyEnabled_ {false};
-    std::string proxyServer_ {};
     std::string proxyServerCached_ {};
 
     std::mutex dhParamsMtx_ {};
     std::shared_future<tls::DhParams> dhParams_;
     std::condition_variable dhParamsCv_;
 
-    bool allowPeersFromHistory_ {true};
-    bool allowPeersFromContact_ {true};
-    bool allowPeersFromTrusted_ {true};
-
-    std::string managerUri_ {};
-    std::string managerUsername_ {};
-
     /**
      * Optional: via_addr construct from received parameters
      */
@@ -830,8 +781,6 @@ private:
     std::shared_ptr<dht::PeerDiscovery> peerDiscovery_;
     std::map<dht::InfoHash, DiscoveredPeer> discoveredPeers_;
     std::map<std::string, std::string> discoveredPeerMap_;
-    bool accountPeerDiscovery_ {false};
-    bool accountPublish_ {false};
 
     /**
      * Avoid to refresh the cache multiple times
@@ -843,7 +792,7 @@ private:
      */
     void cacheTurnServers();
 
-    std::chrono::duration<int> turnRefreshDelay_ {std::chrono::seconds(10)};
+    std::chrono::seconds turnRefreshDelay_ {std::chrono::seconds(10)};
 
     std::set<std::shared_ptr<dht::http::Request>> requests_;
 
diff --git a/src/jamidht/jamiaccount_config.cpp b/src/jamidht/jamiaccount_config.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..905a2d7842e7096a085c7382f475090d81ddcb2a
--- /dev/null
+++ b/src/jamidht/jamiaccount_config.cpp
@@ -0,0 +1,230 @@
+/*
+ *  Copyright (C) 2004-2022 Savoir-faire Linux Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+#include "jamiaccount_config.h"
+#include "account_const.h"
+#include "account_schema.h"
+#include "configkeys.h"
+#include "config/yamlparser.h"
+#include "fileutils.h"
+
+namespace jami {
+
+namespace Conf {
+constexpr const char* const TLS_KEY = "tls";
+constexpr const char* CERTIFICATE_KEY = "certificate";
+constexpr const char* CALIST_KEY = "calist";
+const char* const TLS_PASSWORD_KEY = "password";
+const char* const PRIVATE_KEY_KEY = "privateKey";
+} // namespace Conf
+
+void
+JamiAccountConfig::serialize(YAML::Emitter& out) const
+{
+    out << YAML::BeginMap;
+    SipAccountBaseConfig::serialize(out);
+    out << YAML::Key << Conf::DHT_PORT_KEY << YAML::Value << dhtPort;
+    out << YAML::Key << Conf::DHT_PUBLIC_IN_CALLS << YAML::Value << allowPublicIncoming;
+    out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_HISTORY << YAML::Value << allowPeersFromHistory;
+    out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_CONTACT << YAML::Value << allowPeersFromContact;
+    out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_TRUSTED << YAML::Value << allowPeersFromTrusted;
+    out << YAML::Key << libjami::Account::ConfProperties::DHT_PEER_DISCOVERY << YAML::Value
+        << dhtPeerDiscovery;
+    out << YAML::Key << libjami::Account::ConfProperties::ACCOUNT_PEER_DISCOVERY << YAML::Value
+        << accountPeerDiscovery;
+    out << YAML::Key << libjami::Account::ConfProperties::ACCOUNT_PUBLISH << YAML::Value
+        << accountPublish;
+
+    out << YAML::Key << Conf::PROXY_ENABLED_KEY << YAML::Value << proxyEnabled;
+    out << YAML::Key << Conf::PROXY_SERVER_KEY << YAML::Value << proxyServer;
+    out << YAML::Key << libjami::Account::ConfProperties::DHT_PROXY_LIST_URL << YAML::Value
+        << proxyListUrl;
+
+#if HAVE_RINGNS
+    out << YAML::Key << libjami::Account::ConfProperties::RingNS::URI << YAML::Value << nameServer;
+    if (not registeredName.empty())
+        out << YAML::Key << libjami::Account::VolatileProperties::REGISTERED_NAME << YAML::Value
+            << registeredName;
+#endif
+
+    out << YAML::Key << libjami::Account::ConfProperties::ARCHIVE_PATH << YAML::Value << fileutils::getCleanPath(path, archivePath);
+    out << YAML::Key << libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD << YAML::Value
+        << archiveHasPassword;
+    out << YAML::Key << Conf::RING_ACCOUNT_RECEIPT << YAML::Value << receipt;
+    if (receiptSignature.size() > 0)
+        out << YAML::Key << Conf::RING_ACCOUNT_RECEIPT_SIG << YAML::Value
+            << YAML::Binary(receiptSignature.data(), receiptSignature.size());
+    out << YAML::Key << libjami::Account::ConfProperties::DEVICE_NAME << YAML::Value << deviceName;
+    out << YAML::Key << libjami::Account::ConfProperties::MANAGER_URI << YAML::Value << managerUri;
+    out << YAML::Key << libjami::Account::ConfProperties::MANAGER_USERNAME << YAML::Value
+        << managerUsername;
+
+    // tls submap
+    out << YAML::Key << Conf::TLS_KEY << YAML::Value << YAML::BeginMap;
+    out << YAML::Key << Conf::CALIST_KEY << YAML::Value << fileutils::getCleanPath(path, tlsCaListFile);
+    out << YAML::Key << Conf::CERTIFICATE_KEY << YAML::Value << fileutils::getCleanPath(path, tlsCertificateFile);;
+    out << YAML::Key << Conf::TLS_PASSWORD_KEY << YAML::Value << tlsPassword;
+    out << YAML::Key << Conf::PRIVATE_KEY_KEY << YAML::Value << fileutils::getCleanPath(path, tlsPrivateKeyFile);;
+    out << YAML::EndMap;
+
+    out << YAML::EndMap;
+}
+
+void
+JamiAccountConfig::unserialize(const YAML::Node& node)
+{
+    using yaml_utils::parseValueOptional;
+    using yaml_utils::parsePath;
+    using yaml_utils::parsePathOptional;
+    SipAccountBaseConfig::unserialize(node);
+
+    // get tls submap
+    try {
+        const auto& tlsMap = node[Conf::TLS_KEY];
+        parsePathOptional(tlsMap, Conf::CERTIFICATE_KEY, tlsCertificateFile, path);
+        parsePathOptional(tlsMap, Conf::CALIST_KEY, tlsCaListFile, path);
+        parseValueOptional(tlsMap, Conf::TLS_PASSWORD_KEY, tlsPassword);
+        parsePathOptional(tlsMap, Conf::PRIVATE_KEY_KEY, tlsPrivateKeyFile, path);
+    } catch (...) {
+    }
+    parseValueOptional(node, Conf::DHT_ALLOW_PEERS_FROM_HISTORY, allowPeersFromHistory);
+    parseValueOptional(node, Conf::DHT_ALLOW_PEERS_FROM_CONTACT, allowPeersFromContact);
+    parseValueOptional(node, Conf::DHT_ALLOW_PEERS_FROM_TRUSTED, allowPeersFromTrusted);
+
+    parseValueOptional(node, Conf::PROXY_ENABLED_KEY, proxyEnabled);
+    parseValueOptional(node, Conf::PROXY_SERVER_KEY, proxyServer);
+    parseValueOptional(node, libjami::Account::ConfProperties::DHT_PROXY_LIST_URL, proxyListUrl);
+
+    parseValueOptional(node, libjami::Account::ConfProperties::DEVICE_NAME, deviceName);
+    parseValueOptional(node, libjami::Account::ConfProperties::MANAGER_URI, managerUri);
+    parseValueOptional(node, libjami::Account::ConfProperties::MANAGER_USERNAME, managerUsername);
+
+    parsePathOptional(node, libjami::Account::ConfProperties::ARCHIVE_PATH, archivePath, path);
+    parseValueOptional(node,
+                       libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD,
+                       archiveHasPassword);
+
+    try {
+        parseValueOptional(node, Conf::RING_ACCOUNT_RECEIPT, receipt);
+        auto receipt_sig = node[Conf::RING_ACCOUNT_RECEIPT_SIG].as<YAML::Binary>();
+        receiptSignature = {receipt_sig.data(), receipt_sig.data() + receipt_sig.size()};
+    } catch (const std::exception& e) {
+        JAMI_WARN("can't read receipt: %s", e.what());
+    }
+
+    parseValueOptional(node, libjami::Account::ConfProperties::DHT_PEER_DISCOVERY, dhtPeerDiscovery);
+    parseValueOptional(node,
+                       libjami::Account::ConfProperties::ACCOUNT_PEER_DISCOVERY,
+                       accountPeerDiscovery);
+    parseValueOptional(node, libjami::Account::ConfProperties::ACCOUNT_PUBLISH, accountPublish);
+    parseValueOptional(node, libjami::Account::ConfProperties::RingNS::URI, nameServer);
+    parseValueOptional(node, libjami::Account::VolatileProperties::REGISTERED_NAME, registeredName);
+    parseValueOptional(node, Conf::DHT_PUBLIC_IN_CALLS, allowPublicIncoming);
+}
+
+std::map<std::string, std::string>
+JamiAccountConfig::toMap() const
+{
+    std::map<std::string, std::string> a = SipAccountBaseConfig::toMap();
+    a.emplace(Conf::CONFIG_DHT_PORT, std::to_string(dhtPort));
+    a.emplace(Conf::CONFIG_DHT_PUBLIC_IN_CALLS, allowPublicIncoming ? TRUE_STR : FALSE_STR);
+    a.emplace(libjami::Account::ConfProperties::DHT_PEER_DISCOVERY,
+              dhtPeerDiscovery ? TRUE_STR : FALSE_STR);
+    a.emplace(libjami::Account::ConfProperties::ACCOUNT_PEER_DISCOVERY,
+              accountPeerDiscovery ? TRUE_STR : FALSE_STR);
+    a.emplace(libjami::Account::ConfProperties::ACCOUNT_PUBLISH,
+              accountPublish ? TRUE_STR : FALSE_STR);
+    a.emplace(libjami::Account::ConfProperties::DEVICE_NAME, deviceName);
+    a.emplace(libjami::Account::ConfProperties::Presence::SUPPORT_SUBSCRIBE, TRUE_STR);
+    if (not archivePath.empty() or not managerUri.empty())
+        a.emplace(libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD,
+                  archiveHasPassword ? TRUE_STR : FALSE_STR);
+
+    a.emplace(Conf::CONFIG_TLS_CA_LIST_FILE, fileutils::getFullPath(path, tlsCaListFile));
+    a.emplace(Conf::CONFIG_TLS_CERTIFICATE_FILE, fileutils::getFullPath(path, tlsCertificateFile));
+    a.emplace(Conf::CONFIG_TLS_PRIVATE_KEY_FILE, fileutils::getFullPath(path, tlsPrivateKeyFile));
+    a.emplace(Conf::CONFIG_TLS_PASSWORD, tlsPassword);
+    a.emplace(libjami::Account::ConfProperties::ALLOW_CERT_FROM_HISTORY,
+              allowPeersFromHistory ? TRUE_STR : FALSE_STR);
+    a.emplace(libjami::Account::ConfProperties::ALLOW_CERT_FROM_CONTACT,
+              allowPeersFromContact ? TRUE_STR : FALSE_STR);
+    a.emplace(libjami::Account::ConfProperties::ALLOW_CERT_FROM_TRUSTED,
+              allowPeersFromTrusted ? TRUE_STR : FALSE_STR);
+    a.emplace(libjami::Account::ConfProperties::PROXY_ENABLED, proxyEnabled ? TRUE_STR : FALSE_STR);
+    a.emplace(libjami::Account::ConfProperties::PROXY_SERVER, proxyServer);
+    a.emplace(libjami::Account::ConfProperties::DHT_PROXY_LIST_URL, proxyListUrl);
+    a.emplace(libjami::Account::ConfProperties::MANAGER_URI, managerUri);
+    a.emplace(libjami::Account::ConfProperties::MANAGER_USERNAME, managerUsername);
+#if HAVE_RINGNS
+    a.emplace(libjami::Account::ConfProperties::RingNS::URI, nameServer);
+#endif
+    return a;
+}
+
+void
+JamiAccountConfig::fromMap(const std::map<std::string, std::string>& details)
+{
+    SipAccountBaseConfig::fromMap(details);
+    // TLS
+    parsePath(details, Conf::CONFIG_TLS_CA_LIST_FILE, tlsCaListFile, path);
+    parsePath(details, Conf::CONFIG_TLS_CERTIFICATE_FILE, tlsCertificateFile, path);
+    parsePath(details, Conf::CONFIG_TLS_PRIVATE_KEY_FILE, tlsPrivateKeyFile, path);
+    parseString(details, Conf::CONFIG_TLS_PASSWORD, tlsPassword);
+
+    if (hostname.empty())
+        hostname = DHT_DEFAULT_BOOTSTRAP;
+    parseString(details, libjami::Account::ConfProperties::BOOTSTRAP_LIST_URL, bootstrapListUrl);
+    parseInt(details, Conf::CONFIG_DHT_PORT, dhtPort);
+    parseBool(details, Conf::CONFIG_DHT_PUBLIC_IN_CALLS, allowPublicIncoming);
+    parseBool(details, libjami::Account::ConfProperties::DHT_PEER_DISCOVERY, dhtPeerDiscovery);
+    parseBool(details,
+              libjami::Account::ConfProperties::ACCOUNT_PEER_DISCOVERY,
+              accountPeerDiscovery);
+    parseBool(details, libjami::Account::ConfProperties::ACCOUNT_PUBLISH, accountPublish);
+    parseBool(details,
+              libjami::Account::ConfProperties::ALLOW_CERT_FROM_HISTORY,
+              allowPeersFromHistory);
+    parseBool(details,
+              libjami::Account::ConfProperties::ALLOW_CERT_FROM_CONTACT,
+              allowPeersFromContact);
+    parseBool(details,
+              libjami::Account::ConfProperties::ALLOW_CERT_FROM_TRUSTED,
+              allowPeersFromTrusted);
+
+    parseString(details, libjami::Account::ConfProperties::MANAGER_URI, managerUri);
+    parseString(details, libjami::Account::ConfProperties::MANAGER_USERNAME, managerUsername);
+    // parseString(details, libjami::Account::ConfProperties::USERNAME, username);
+
+    parseString(details, libjami::Account::ConfProperties::ARCHIVE_PASSWORD, archive_password);
+    parseString(details, libjami::Account::ConfProperties::ARCHIVE_PIN, archive_pin);
+    std::transform(archive_pin.begin(), archive_pin.end(), archive_pin.begin(), ::toupper);
+    parseString(details, libjami::Account::ConfProperties::ARCHIVE_PATH, archive_path);
+    parseString(details, libjami::Account::ConfProperties::DEVICE_NAME, deviceName);
+
+    auto oldProxyServer = proxyServer, oldProxyServerList = proxyListUrl;
+    parseString(details, libjami::Account::ConfProperties::DHT_PROXY_LIST_URL, proxyListUrl);
+    parseBool(details, libjami::Account::ConfProperties::PROXY_ENABLED, proxyEnabled);
+    parseString(details, libjami::Account::ConfProperties::PROXY_SERVER, proxyServer);
+    if (not managerUri.empty() and managerUri.rfind("http", 0) != 0) {
+        managerUri = "https://" + managerUri;
+    }
+
+#if HAVE_RINGNS
+    parseString(details, libjami::Account::ConfProperties::RingNS::URI, nameServer);
+#endif
+}
+
+} // namespace jami
diff --git a/src/jamidht/jamiaccount_config.h b/src/jamidht/jamiaccount_config.h
new file mode 100644
index 0000000000000000000000000000000000000000..dd6d3c8d95b259e02c822f9a8d09796c48c5cbad
--- /dev/null
+++ b/src/jamidht/jamiaccount_config.h
@@ -0,0 +1,77 @@
+/*
+ *  Copyright (C) 2004-2022 Savoir-faire Linux Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+#pragma once
+#include "sip/sipaccountbase_config.h"
+
+namespace jami {
+constexpr static const char* ACCOUNT_TYPE_JAMI = "RING";
+constexpr static const char* const DHT_DEFAULT_BOOTSTRAP = "bootstrap.jami.net";
+constexpr static const char* DEFAULT_TURN_SERVER = "turn.jami.net";
+constexpr static const char* DEFAULT_TURN_USERNAME = "ring";
+constexpr static const char* DEFAULT_TURN_PWD = "ring";
+constexpr static const char* DEFAULT_TURN_REALM = "ring";
+
+struct JamiAccountConfig : public SipAccountBaseConfig {
+    JamiAccountConfig(const std::string& id = {}, const std::string& path = {}): SipAccountBaseConfig(ACCOUNT_TYPE_JAMI, id, path) {
+        // Default values specific to Jami accounts
+        hostname = DHT_DEFAULT_BOOTSTRAP;
+        turnServer = DEFAULT_TURN_SERVER;
+        turnServerUserName = DEFAULT_TURN_USERNAME;
+        turnServerPwd = DEFAULT_TURN_PWD;
+        turnServerRealm = DEFAULT_TURN_REALM;
+        turnEnabled = true;
+        upnpEnabled = true;
+    }
+    void serialize(YAML::Emitter& out) const override;
+    void unserialize(const YAML::Node& node) override;
+    std::map<std::string, std::string> toMap() const override;
+    void fromMap(const std::map<std::string, std::string>&) override;
+
+    std::string deviceName {};
+    uint16_t dhtPort {0};
+    bool dhtPeerDiscovery {false};
+    bool accountPeerDiscovery {false};
+    bool accountPublish {false};
+    std::string bootstrapListUrl {"https://config.jami.net/boostrapList"};
+
+    bool proxyEnabled {false};
+    std::string proxyServer {"dhtproxy.jami.net:[80-95]"};
+    std::string proxyListUrl {"https://config.jami.net/proxyList"};
+
+    std::string nameServer {};
+    std::string registeredName {};
+
+    bool allowPeersFromHistory {true};
+    bool allowPeersFromContact {true};
+    bool allowPeersFromTrusted {true};
+    bool allowPublicIncoming {true};
+
+    std::string managerUri {};
+    std::string managerUsername {};
+
+    std::string archivePath {"archive.gz"};
+    bool archiveHasPassword {true};
+    // not saved, only used client->daemon
+    std::string archive_password;
+    std::string archive_pin;
+    std::string archive_path;
+
+    std::string receipt {};
+    std::vector<uint8_t> receiptSignature {};
+};
+
+}
diff --git a/src/manager.cpp b/src/manager.cpp
index 1f92858d89fc81ac7b8225041c48db54bd89d8d5..b82e9f51326f620239b46ef6d924fec065fcd75c 100644
--- a/src/manager.cpp
+++ b/src/manager.cpp
@@ -43,7 +43,6 @@
 #include "account.h"
 #include "string_utils.h"
 #include "jamidht/jamiaccount.h"
-#include "sip/sipvoiplink.h"
 #include "account.h"
 #include <opendht/rng.h>
 using random_device = dht::crypto::random_device;
@@ -52,6 +51,7 @@ using random_device = dht::crypto::random_device;
 
 #include "connectivity/sip_utils.h"
 #include "sip/sipvoiplink.h"
+#include "sip/sipaccount_config.h"
 
 #include "im/instant_messaging.h"
 
@@ -628,23 +628,26 @@ void
 Manager::ManagerPimpl::loadAccount(const YAML::Node& node, int& errorCount)
 {
     using yaml_utils::parseValue;
-
-    std::string accountType;
-    parseValue(node, "type", accountType);
+    using yaml_utils::parseValueOptional;
 
     std::string accountid;
     parseValue(node, "id", accountid);
 
+    std::string accountType = ACCOUNT_TYPE_SIP;
+    parseValueOptional(node, "type", accountType);
+
     if (!accountid.empty()) {
-        if (base_.accountFactory.isSupportedType(accountType.c_str())) {
+        if (base_.accountFactory.isSupportedType(accountType)) {
             if (auto a = base_.accountFactory.createAccount(accountType.c_str(), accountid)) {
-                a->unserialize(node);
+                auto config = a->buildConfig();
+                config->unserialize(node);
+                a->setConfig(std::move(config));
             } else {
-                JAMI_ERR("Failed to create account type \"%s\"", accountType.c_str());
+                JAMI_ERROR("Failed to create account of type \"{:s}\"", accountType);
                 ++errorCount;
             }
         } else {
-            JAMI_WARN("Ignoring unknown account type \"%s\"", accountType.c_str());
+            JAMI_WARNING("Ignoring unknown account type \"{:s}\"", accountType);
         }
     }
 }
@@ -1655,15 +1658,6 @@ Manager::ioContext() const
     return pimpl_->ioContext_;
 }
 
-void
-Manager::addTask(std::function<bool()>&& task, const char* filename, uint32_t linum)
-{
-    pimpl_->scheduler_.scheduleAtFixedRate(std::move(task),
-                                           std::chrono::milliseconds(30),
-                                           filename,
-                                           linum);
-}
-
 std::shared_ptr<Task>
 Manager::scheduleTask(std::function<void()>&& task,
                       std::chrono::steady_clock::time_point when,
@@ -1682,13 +1676,6 @@ Manager::scheduleTaskIn(std::function<void()>&& task,
     return pimpl_->scheduler_.scheduleIn(std::move(task), timeout, filename, linum);
 }
 
-// Must be invoked periodically by a timer from the main event loop
-void
-Manager::pollEvents()
-{}
-
-// THREAD=Main
-
 void
 Manager::saveConfig(const std::shared_ptr<Account>& acc)
 {
@@ -1724,7 +1711,7 @@ Manager::saveConfig()
                     saveConfig(ringAccount);
                 }
             } else {
-                account->serialize(out);
+                account->config().serialize(out);
             }
         }
         out << YAML::EndSeq;
@@ -1743,7 +1730,7 @@ Manager::saveConfig()
 
         std::lock_guard<std::mutex> lock(fileutils::getFileLock(pimpl_->path_));
         std::ofstream fout = fileutils::ofstream(pimpl_->path_);
-        fout << out.c_str();
+        fout.write(out.c_str(), out.size());
     } catch (const YAML::Exception& e) {
         JAMI_ERR("%s", e.what());
     } catch (const std::runtime_error& e) {
@@ -2098,14 +2085,6 @@ Manager::playRingtone(const std::string& accountID)
         return;
     }
 
-    std::string ringtone = account->getRingtonePath();
-    if (ringtone.find(DIR_SEPARATOR_CH) == std::string::npos) {
-        // A base file name was provided (such as the default); try to
-        // resolve it from Jami's data installation prefix.
-        ringtone = std::string(JAMI_DATADIR) + DIR_SEPARATOR_STR + RINGDIR + DIR_SEPARATOR_STR
-                   + ringtone;
-    }
-
     {
         std::lock_guard<std::mutex> lock(pimpl_->audioLayerMutex_);
 
@@ -2119,7 +2098,7 @@ Manager::playRingtone(const std::string& accountID)
         pimpl_->toneCtrl_.setSampleRate(pimpl_->audiodriver_->getSampleRate());
     }
 
-    if (not pimpl_->toneCtrl_.setAudioFile(ringtone))
+    if (not pimpl_->toneCtrl_.setAudioFile(account->getRingtonePath()))
         ringback();
 }
 
@@ -2245,7 +2224,7 @@ AudioDeviceGuard::AudioDeviceGuard(Manager& manager, AudioDeviceType type)
     auto streamId = (unsigned) type;
     if (streamId >= manager_.pimpl_->audioStreamUsers_.size())
         throw std::invalid_argument("Invalid audio device type");
-    if (manager_.pimpl_->audioStreamUsers_[(unsigned) type]++ == 0) {
+    if (manager_.pimpl_->audioStreamUsers_[streamId]++ == 0) {
         if (auto layer = manager_.getAudioDriver())
             layer->startStream(type);
     }
@@ -2658,18 +2637,14 @@ Manager::getVolatileAccountDetails(const std::string& accountID) const
     }
 }
 
-// method to reduce the if/else mess.
-// Even better, switch to XML !
-
 void
 Manager::setAccountDetails(const std::string& accountID,
                            const std::map<std::string, std::string>& details)
 {
     JAMI_DBG("Set account details for %s", accountID.c_str());
 
-    const auto account = getAccount(accountID);
-
-    if (account == nullptr) {
+    auto account = getAccount(accountID);
+    if (not account) {
         JAMI_ERR("Could not find account %s", accountID.c_str());
         return;
     }
@@ -2679,16 +2654,8 @@ Manager::setAccountDetails(const std::string& accountID,
         return;
 
     // Unregister before modifying any account information
-    // FIXME: inefficient api, don't pass details (not as ref nor copy)
-    // let client requiests them we needed.
     account->doUnregister([&](bool /* transport_free */) {
         account->setAccountDetails(details);
-        // Serialize configuration to disk once it is done
-        if (auto ringAccount = std::dynamic_pointer_cast<JamiAccount>(account)) {
-            saveConfig(ringAccount);
-        } else {
-            saveConfig();
-        }
 
         if (account->isUsable())
             account->doRegister();
@@ -2839,10 +2806,9 @@ Manager::loadAccountMap(const YAML::Node& node)
             if (fileutils::isFile(configFile)) {
                 try {
                     if (auto a = accountFactory.createAccount(JamiAccount::ACCOUNT_TYPE, dir)) {
-                        std::ifstream file = fileutils::ifstream(configFile);
-                        YAML::Node parsedConfig = YAML::Load(file);
-                        file.close();
-                        a->unserialize(parsedConfig);
+                        auto config = a->buildConfig();
+                        config->unserialize(YAML::LoadFile(configFile));
+                        a->setConfig(std::move(config));
                     }
                 } catch (const std::exception& e) {
                     JAMI_ERR("Can't import account %s: %s", dir.c_str(), e.what());
@@ -2904,8 +2870,6 @@ Manager::sendRegister(const std::string& accountID, bool enable)
         return;
 
     acc->setEnabled(enable);
-    acc->loadConfig();
-
     saveConfig(acc);
 
     if (acc->isEnabled()) {
@@ -3208,13 +3172,10 @@ Manager::getDefaultModerators(const std::string& accountID)
 void
 Manager::enableLocalModerators(const std::string& accountID, bool isModEnabled)
 {
-    auto acc = getAccount(accountID);
-    if (!acc) {
-        JAMI_ERR("Fail to set local moderators, account %s not found", accountID.c_str());
-        return;
-    }
-    acc->enableLocalModerators(isModEnabled);
-    saveConfig(acc);
+    if (auto acc = getAccount(accountID))
+        acc->editConfig([&](AccountConfig& config){
+            config.localModeratorsEnabled = isModEnabled;
+        });
 }
 
 bool
@@ -3231,13 +3192,10 @@ Manager::isLocalModeratorsEnabled(const std::string& accountID)
 void
 Manager::setAllModerators(const std::string& accountID, bool allModerators)
 {
-    auto acc = getAccount(accountID);
-    if (!acc) {
-        JAMI_ERR("Fail to set all moderators, account %s not found", accountID.c_str());
-        return;
-    }
-    acc->setAllModerators(allModerators);
-    saveConfig(acc);
+    if (auto acc = getAccount(accountID))
+        acc->editConfig([&](AccountConfig& config){
+            config.allModeratorsEnabled = allModerators;
+        });
 }
 
 bool
diff --git a/src/manager.h b/src/manager.h
index 124ea80a9957aeb331f70b6d5b69accd54993f60..7d251c0e57921bdfb8361e7651ce63db402bf99c 100644
--- a/src/manager.h
+++ b/src/manager.h
@@ -765,10 +765,6 @@ public:
      */
     void unregisterAccounts();
 
-    /**
-     * Call periodically to poll for VoIP events */
-    void pollEvents();
-
     /**
      * Create a new outgoing call
      * @param toUrl Destination address
@@ -789,10 +785,6 @@ public:
 
     std::shared_ptr<asio::io_context> ioContext() const;
 
-    void addTask(std::function<bool()>&& task,
-                 const char* filename = CURRENT_FILENAME(),
-                 uint32_t linum = CURRENT_LINE());
-
     std::shared_ptr<Task> scheduleTask(std::function<void()>&& task,
                                        std::chrono::steady_clock::time_point when,
                                        const char* filename = CURRENT_FILENAME(),
diff --git a/src/meson.build b/src/meson.build
index 86452aea04ecfeb381032daca8ec6c8d952e2d12..fd0fe308f80da5ba9a674972dd6e9bc94ce350c6 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -49,6 +49,7 @@ libjami_sources = files(
     'jamidht/conversationrepository.cpp',
     'jamidht/gitserver.cpp',
     'jamidht/jamiaccount.cpp',
+    'jamidht/jamiaccount_config.cpp',
     'jamidht/namedirectory.cpp',
     'jamidht/p2p.cpp',
     'jamidht/server_account_manager.cpp',
@@ -93,6 +94,7 @@ libjami_sources = files(
     'media/srtp.c',
     'media/system_codec_container.cpp',
     'account.cpp',
+    'account_config.cpp',
     'account_factory.cpp',
     'archiver.cpp',
     'base64.cpp',
@@ -115,6 +117,8 @@ libjami_sources = files(
     'sip/sdp.cpp',
     'sip/sipaccountbase.cpp',
     'sip/sipaccount.cpp',
+    'sip/sipaccountbase_config.cpp',
+    'sip/sipaccount_config.cpp',
     'sip/sipcall.cpp',
     'sip/sippresence.cpp',
     'sip/siptransport.cpp',
diff --git a/src/sip/CMakeLists.txt b/src/sip/CMakeLists.txt
index 329b760d5d409fbc3054ec5ca5469d2d366f1c95..9da37a07a65d58fcf9666bfb33028e1471e8f655 100644
--- a/src/sip/CMakeLists.txt
+++ b/src/sip/CMakeLists.txt
@@ -14,6 +14,10 @@ list (APPEND Source_Files__sip
       "${CMAKE_CURRENT_SOURCE_DIR}/sipaccount.h"
       "${CMAKE_CURRENT_SOURCE_DIR}/sipaccountbase.cpp"
       "${CMAKE_CURRENT_SOURCE_DIR}/sipaccountbase.h"
+      "${CMAKE_CURRENT_SOURCE_DIR}/sipaccount_config.cpp"
+      "${CMAKE_CURRENT_SOURCE_DIR}/sipaccount_config.h"
+      "${CMAKE_CURRENT_SOURCE_DIR}/sipaccountbase_config.cpp"
+      "${CMAKE_CURRENT_SOURCE_DIR}/sipaccountbase_config.h"
       "${CMAKE_CURRENT_SOURCE_DIR}/sipcall.cpp"
       "${CMAKE_CURRENT_SOURCE_DIR}/sipcall.h"
       "${CMAKE_CURRENT_SOURCE_DIR}/sippresence.cpp"
diff --git a/src/sip/Makefile.am b/src/sip/Makefile.am
index 9193ef9ddd8e4ef17d77964c809aebaf2b35af9f..51a03797fe5092c4916856a91b770f3011779e59 100644
--- a/src/sip/Makefile.am
+++ b/src/sip/Makefile.am
@@ -10,6 +10,10 @@ libsiplink_la_SOURCES = \
 	./sip/sipvoiplink.cpp \
 	./sip/siptransport.cpp \
 	./sip/sipaccountbase.h \
+	./sip/sipaccountbase_config.h \
+	./sip/sipaccountbase_config.cpp \
+	./sip/sipaccount_config.h \
+	./sip/sipaccount_config.cpp \
 	./sip/sdp.h \
 	./sip/sipaccount.h \
 	./sip/sipcall.h \
diff --git a/src/sip/sipaccount.cpp b/src/sip/sipaccount.cpp
index 071511ceb90a313411dbf7d3e67f4fb01e2ddf18..695b71839e13684b3dd4f8aa4b7e9d177441a4b1 100644
--- a/src/sip/sipaccount.cpp
+++ b/src/sip/sipaccount.cpp
@@ -85,11 +85,8 @@ namespace jami {
 
 using yaml_utils::parseValue;
 using yaml_utils::parseValueOptional;
-using yaml_utils::parseVectorMap;
 using sip_utils::CONST_PJ_STR;
 
-static constexpr int MIN_REGISTRATION_TIME = 60;                  // seconds
-static constexpr unsigned DEFAULT_REGISTRATION_EXPIRE = 3600;     // seconds
 static constexpr unsigned REGISTRATION_FIRST_RETRY_INTERVAL = 60; // seconds
 static constexpr unsigned REGISTRATION_RETRY_INTERVAL = 300;      // seconds
 static const char* const VALID_TLS_PROTOS[] = {"Default", "TLSv1.2", "TLSv1.1", "TLSv1"};
@@ -125,36 +122,12 @@ registration_cb(pjsip_regc_cbparam* param)
 
 SIPAccount::SIPAccount(const std::string& accountID, bool presenceEnabled)
     : SIPAccountBase(accountID)
-    , auto_rereg_()
-    , credentials_()
-    , regc_(nullptr)
-    , bRegister_(false)
-    , registrationExpire_(DEFAULT_REGISTRATION_EXPIRE)
-    , serviceRoute_()
-    , cred_()
-    , tlsSetting_()
     , ciphers_(100)
-    , tlsMethod_("TLSv1")
-    , tlsCiphers_()
-    , tlsServerName_("")
-    , tlsVerifyServer_(false)
-    , tlsVerifyClient_(true)
-    , tlsRequireClientCertificate_(true)
-    , tlsDisableSecureDlgCheck_(true)
-    , tlsNegotiationTimeoutSec_("2")
-    , registrationStateDetailed_()
-    , registrationRefreshEnabled_(true)
-    , receivedParameter_("")
-    , rPort_(-1)
-    , via_addr_()
-    , allowIPAutoRewrite_(true)
-    , via_tp_(nullptr)
     , presence_(presenceEnabled ? new SIPPresence(this) : nullptr)
 {
     via_addr_.host.ptr = 0;
     via_addr_.host.slen = 0;
     via_addr_.port = 0;
-    setActiveCodecs({});
 }
 
 SIPAccount::~SIPAccount() noexcept
@@ -314,7 +287,7 @@ SIPAccount::onTransportStateChanged(pjsip_transport_state state,
     // Notify the client of the new transport state
     if (currentStatus != transportStatus_)
         emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(accountID_,
-                                                                       getVolatileAccountDetails());
+                                                                         getVolatileAccountDetails());
 }
 
 void
@@ -428,68 +401,13 @@ SIPAccount::SIPStartCall(std::shared_ptr<SIPCall>& call)
     return true;
 }
 
-void
-SIPAccount::serialize(YAML::Emitter& out) const
-{
-    std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
-
-    out << YAML::BeginMap;
-    SIPAccountBase::serialize(out);
-
-    out << YAML::Key << Conf::BIND_ADDRESS_KEY << YAML::Value << bindAddress_;
-    out << YAML::Key << Conf::PORT_KEY << YAML::Value << localPort_;
-
-    out << YAML::Key << USERNAME_KEY << YAML::Value << username_;
-
-    // each credential is a map, and we can have multiple credentials
-    out << YAML::Key << Conf::CRED_KEY << YAML::Value << getCredentials();
-
-    out << YAML::Key << Conf::KEEP_ALIVE_ENABLED << YAML::Value << registrationRefreshEnabled_;
-
-    out << YAML::Key << PRESENCE_MODULE_ENABLED_KEY << YAML::Value
-        << (presence_ and presence_->isEnabled());
-    out << YAML::Key << Conf::PRESENCE_PUBLISH_SUPPORTED_KEY << YAML::Value
-        << (presence_ and presence_->isSupported(PRESENCE_FUNCTION_PUBLISH));
-    out << YAML::Key << Conf::PRESENCE_SUBSCRIBE_SUPPORTED_KEY << YAML::Value
-        << (presence_ and presence_->isSupported(PRESENCE_FUNCTION_SUBSCRIBE));
-
-    out << YAML::Key << Conf::CONFIG_ACCOUNT_REGISTRATION_EXPIRE << YAML::Value
-        << registrationExpire_;
-    out << YAML::Key << Conf::SERVICE_ROUTE_KEY << YAML::Value << serviceRoute_;
-    out << YAML::Key << Conf::ALLOW_IP_AUTO_REWRITE << YAML::Value << allowIPAutoRewrite_;
-
-    // tls submap
-    out << YAML::Key << Conf::TLS_KEY << YAML::Value << YAML::BeginMap;
-    SIPAccountBase::serializeTls(out);
-    out << YAML::Key << Conf::TLS_ENABLE_KEY << YAML::Value << tlsEnable_;
-    out << YAML::Key << Conf::TLS_PORT_KEY << YAML::Value << tlsListenerPort_;
-    out << YAML::Key << Conf::VERIFY_CLIENT_KEY << YAML::Value << tlsVerifyClient_;
-    out << YAML::Key << Conf::VERIFY_SERVER_KEY << YAML::Value << tlsVerifyServer_;
-    out << YAML::Key << Conf::REQUIRE_CERTIF_KEY << YAML::Value << tlsRequireClientCertificate_;
-    out << YAML::Key << Conf::DISABLE_SECURE_DLG_CHECK << YAML::Value << tlsDisableSecureDlgCheck_;
-    out << YAML::Key << Conf::TIMEOUT_KEY << YAML::Value << tlsNegotiationTimeoutSec_;
-    out << YAML::Key << Conf::CIPHERS_KEY << YAML::Value << tlsCiphers_;
-    out << YAML::Key << Conf::METHOD_KEY << YAML::Value << tlsMethod_;
-    out << YAML::Key << Conf::SERVER_KEY << YAML::Value << tlsServerName_;
-    out << YAML::EndMap;
-
-    // srtp submap
-    out << YAML::Key << Conf::SRTP_KEY << YAML::Value << YAML::BeginMap;
-    out << YAML::Key << Conf::KEY_EXCHANGE_KEY << YAML::Value
-        << sip_utils::getKeyExchangeName(srtpKeyExchange_);
-    out << YAML::Key << Conf::RTP_FALLBACK_KEY << YAML::Value << srtpFallback_;
-    out << YAML::EndMap;
-
-    out << YAML::EndMap;
-}
-
 void
 SIPAccount::usePublishedAddressPortInVIA()
 {
     publishedIpStr_ = getPublishedIpAddress().toString();
     via_addr_.host.ptr = (char*) publishedIpStr_.c_str();
     via_addr_.host.slen = publishedIpStr_.size();
-    via_addr_.port = publishedPort_;
+    via_addr_.port = publishedPortUsed_;
 }
 
 void
@@ -513,207 +431,6 @@ validate(std::string& member, const std::string& param, const T& valid)
         JAMI_ERR("Invalid parameter \"%s\"", param.c_str());
 }
 
-void
-SIPAccount::unserialize(const YAML::Node& node)
-{
-    std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
-
-    SIPAccountBase::unserialize(node);
-    parseValue(node, USERNAME_KEY, username_);
-
-    parseValue(node, Conf::BIND_ADDRESS_KEY, bindAddress_);
-
-    int port = sip_utils::DEFAULT_SIP_PORT;
-    parseValue(node, Conf::PORT_KEY, port);
-    localPort_ = port;
-
-    if (not isIP2IP()) {
-        unsigned expire = 0;
-        if (not parseValueOptional(node, Conf::CONFIG_ACCOUNT_REGISTRATION_EXPIRE, expire)) {
-            // Proably using an older config file.
-            parseValueOptional(node, Preferences::REGISTRATION_EXPIRE_KEY, expire);
-        }
-        setRegistrationExpire(expire);
-
-        parseValue(node, Conf::KEEP_ALIVE_ENABLED, registrationRefreshEnabled_);
-        parseValue(node, Conf::SERVICE_ROUTE_KEY, serviceRoute_);
-        parseValueOptional(node, Conf::ALLOW_IP_AUTO_REWRITE, allowIPAutoRewrite_);
-
-        const auto& credsNode = node[Conf::CRED_KEY];
-        setCredentials(parseVectorMap(credsNode,
-                                      {Conf::CONFIG_ACCOUNT_REALM,
-                                       Conf::CONFIG_ACCOUNT_USERNAME,
-                                       Conf::CONFIG_ACCOUNT_PASSWORD}));
-    }
-
-    bool presEnabled = false;
-    parseValue(node, PRESENCE_MODULE_ENABLED_KEY, presEnabled);
-    enablePresence(presEnabled);
-    bool publishSupported = false;
-    parseValue(node, Conf::PRESENCE_PUBLISH_SUPPORTED_KEY, publishSupported);
-    bool subscribeSupported = false;
-    parseValue(node, Conf::PRESENCE_SUBSCRIBE_SUPPORTED_KEY, subscribeSupported);
-    if (presence_) {
-        presence_->support(PRESENCE_FUNCTION_PUBLISH, publishSupported);
-        presence_->support(PRESENCE_FUNCTION_SUBSCRIBE, subscribeSupported);
-    }
-
-    // Init stun server name with default server name
-    stunServerName_ = pj_str((char*) stunServer_.data());
-
-    const auto& credsNode = node[Conf::CRED_KEY];
-    setCredentials(parseVectorMap(credsNode,
-                                  {Conf::CONFIG_ACCOUNT_REALM,
-                                   Conf::CONFIG_ACCOUNT_USERNAME,
-                                   Conf::CONFIG_ACCOUNT_PASSWORD}));
-
-    // get tls submap
-    const auto& tlsMap = node[Conf::TLS_KEY];
-    parseValue(tlsMap, Conf::CERTIFICATE_KEY, tlsCertificateFile_);
-    parseValue(tlsMap, Conf::CALIST_KEY, tlsCaListFile_);
-    parseValue(tlsMap, Conf::TLS_PASSWORD_KEY, tlsPassword_);
-    parseValue(tlsMap, Conf::PRIVATE_KEY_KEY, tlsPrivateKeyFile_);
-    parseValue(tlsMap, Conf::TLS_ENABLE_KEY, tlsEnable_);
-    parseValue(tlsMap, Conf::TLS_PORT_KEY, tlsListenerPort_);
-    parseValue(tlsMap, Conf::CIPHERS_KEY, tlsCiphers_);
-
-    std::string tmpMethod(tlsMethod_);
-    parseValue(tlsMap, Conf::METHOD_KEY, tmpMethod);
-    validate(tlsMethod_, tmpMethod, VALID_TLS_PROTOS);
-
-    parseValue(tlsMap, Conf::SERVER_KEY, tlsServerName_);
-    parseValue(tlsMap, Conf::REQUIRE_CERTIF_KEY, tlsRequireClientCertificate_);
-    parseValueOptional(tlsMap, Conf::DISABLE_SECURE_DLG_CHECK, tlsDisableSecureDlgCheck_);
-    parseValue(tlsMap, Conf::VERIFY_CLIENT_KEY, tlsVerifyClient_);
-    parseValue(tlsMap, Conf::VERIFY_SERVER_KEY, tlsVerifyServer_);
-    // FIXME
-    parseValue(tlsMap, Conf::TIMEOUT_KEY, tlsNegotiationTimeoutSec_);
-
-    // get srtp submap
-    const auto& srtpMap = node[Conf::SRTP_KEY];
-    std::string tmpKey;
-    parseValue(srtpMap, Conf::KEY_EXCHANGE_KEY, tmpKey);
-    srtpKeyExchange_ = sip_utils::getKeyExchangeProtocol(tmpKey.c_str());
-    parseValue(srtpMap, Conf::RTP_FALLBACK_KEY, srtpFallback_);
-}
-
-void
-SIPAccount::setAccountDetails(const std::map<std::string, std::string>& details)
-{
-    std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
-
-    SIPAccountBase::setAccountDetails(details);
-    parseString(details, Conf::CONFIG_ACCOUNT_USERNAME, username_);
-
-    parseInt(details, Conf::CONFIG_LOCAL_PORT, localPort_);
-
-    // TLS
-    parseString(details, Conf::CONFIG_TLS_CA_LIST_FILE, tlsCaListFile_);
-    parseString(details, Conf::CONFIG_TLS_CERTIFICATE_FILE, tlsCertificateFile_);
-    parseString(details, Conf::CONFIG_TLS_PRIVATE_KEY_FILE, tlsPrivateKeyFile_);
-    parseString(details, Conf::CONFIG_TLS_PASSWORD, tlsPassword_);
-
-    // SIP specific account settings
-    parseString(details, Conf::CONFIG_BIND_ADDRESS, bindAddress_);
-    parseString(details, Conf::CONFIG_ACCOUNT_ROUTESET, serviceRoute_);
-    parseBool(details, Conf::CONFIG_ACCOUNT_IP_AUTO_REWRITE, allowIPAutoRewrite_);
-
-    unsigned expire = 0;
-    parseInt(details, Conf::CONFIG_ACCOUNT_REGISTRATION_EXPIRE, expire);
-    setRegistrationExpire(expire);
-
-    parseBool(details, Conf::CONFIG_KEEP_ALIVE_ENABLED, registrationRefreshEnabled_);
-    bool presenceEnabled = false;
-    parseBool(details, Conf::CONFIG_PRESENCE_ENABLED, presenceEnabled);
-    enablePresence(presenceEnabled);
-
-    // TLS settings
-    parseBool(details, Conf::CONFIG_TLS_ENABLE, tlsEnable_);
-    parseInt(details, Conf::CONFIG_TLS_LISTENER_PORT, tlsListenerPort_);
-    auto iter = details.find(Conf::CONFIG_TLS_METHOD);
-    if (iter != details.end())
-        validate(tlsMethod_, iter->second, VALID_TLS_PROTOS);
-    parseString(details, Conf::CONFIG_TLS_CIPHERS, tlsCiphers_);
-    parseString(details, Conf::CONFIG_TLS_SERVER_NAME, tlsServerName_);
-    parseBool(details, Conf::CONFIG_TLS_VERIFY_SERVER, tlsVerifyServer_);
-    parseBool(details, Conf::CONFIG_TLS_VERIFY_CLIENT, tlsVerifyClient_);
-    parseBool(details, Conf::CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, tlsRequireClientCertificate_);
-    parseBool(details, Conf::CONFIG_TLS_DISABLE_SECURE_DLG_CHECK, tlsDisableSecureDlgCheck_);
-    parseString(details, Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, tlsNegotiationTimeoutSec_);
-    parseBool(details, Conf::CONFIG_TLS_VERIFY_SERVER, tlsVerifyServer_);
-    parseBool(details, Conf::CONFIG_TLS_VERIFY_CLIENT, tlsVerifyClient_);
-    parseBool(details, Conf::CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, tlsRequireClientCertificate_);
-    parseString(details, Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, tlsNegotiationTimeoutSec_);
-
-    // srtp settings
-    parseBool(details, Conf::CONFIG_SRTP_RTP_FALLBACK, srtpFallback_);
-    iter = details.find(Conf::CONFIG_SRTP_KEY_EXCHANGE);
-    if (iter != details.end())
-        srtpKeyExchange_ = sip_utils::getKeyExchangeProtocol(iter->second.c_str());
-
-    if (credentials_.empty()) { // credentials not set, construct 1 entry
-        JAMI_WARN("No credentials set, inferring them...");
-        std::vector<std::map<std::string, std::string>> v;
-        std::map<std::string, std::string> map;
-        map[Conf::CONFIG_ACCOUNT_USERNAME] = username_;
-        parseString(details, Conf::CONFIG_ACCOUNT_PASSWORD, map[Conf::CONFIG_ACCOUNT_PASSWORD]);
-        map[Conf::CONFIG_ACCOUNT_REALM] = "*";
-        v.push_back(map);
-        setCredentials(v);
-    }
-    if (isIP2IP()) {
-        localPort_ = (localPort_ == sip_utils::DEFAULT_AUTO_SELECT_PORT)
-                         ? sip_utils::DEFAULT_SIP_PORT
-                         : localPort_;
-        tlsListenerPort_ = (tlsListenerPort_ == sip_utils::DEFAULT_AUTO_SELECT_PORT)
-                               ? sip_utils::DEFAULT_SIP_TLS_PORT
-                               : tlsListenerPort_;
-    }
-}
-
-std::map<std::string, std::string>
-SIPAccount::getAccountDetails() const
-{
-    std::lock_guard<std::recursive_mutex> lock(configurationMutex_);
-
-    auto a = SIPAccountBase::getAccountDetails();
-
-    std::string password {};
-    if (hasCredentials()) {
-        for (const auto& cred : credentials_)
-            if (cred.username == username_) {
-                password = cred.password;
-                break;
-            }
-    }
-    a.emplace(Conf::CONFIG_ACCOUNT_PASSWORD, std::move(password));
-
-    a.emplace(Conf::CONFIG_BIND_ADDRESS, bindAddress_);
-    a.emplace(Conf::CONFIG_LOCAL_PORT, std::to_string(localPort_));
-    a.emplace(Conf::CONFIG_ACCOUNT_ROUTESET, serviceRoute_);
-    a.emplace(Conf::CONFIG_ACCOUNT_IP_AUTO_REWRITE, allowIPAutoRewrite_ ? TRUE_STR : FALSE_STR);
-    a.emplace(Conf::CONFIG_ACCOUNT_REGISTRATION_EXPIRE, std::to_string(registrationExpire_));
-    a.emplace(Conf::CONFIG_KEEP_ALIVE_ENABLED, registrationRefreshEnabled_ ? TRUE_STR : FALSE_STR);
-
-    a.emplace(Conf::CONFIG_PRESENCE_ENABLED,
-              presence_ and presence_->isEnabled() ? TRUE_STR : FALSE_STR);
-    a.emplace(Conf::CONFIG_PRESENCE_PUBLISH_SUPPORTED,
-              presence_ and presence_->isSupported(PRESENCE_FUNCTION_PUBLISH) ? TRUE_STR
-                                                                              : FALSE_STR);
-    a.emplace(Conf::CONFIG_PRESENCE_SUBSCRIBE_SUPPORTED,
-              presence_ and presence_->isSupported(PRESENCE_FUNCTION_SUBSCRIBE) ? TRUE_STR
-                                                                                : FALSE_STR);
-
-    auto tlsSettings(getTlsSettings());
-    a.insert(tlsSettings.begin(), tlsSettings.end());
-
-    a.emplace(Conf::CONFIG_SRTP_KEY_EXCHANGE, sip_utils::getKeyExchangeName(srtpKeyExchange_));
-    a.emplace(Conf::CONFIG_SRTP_ENABLE, isSrtpEnabled() ? TRUE_STR : FALSE_STR);
-    a.emplace(Conf::CONFIG_SRTP_RTP_FALLBACK, srtpFallback_ ? TRUE_STR : FALSE_STR);
-
-    return a;
-}
-
 std::map<std::string, std::string>
 SIPAccount::getVolatileAccountDetails() const
 {
@@ -752,13 +469,13 @@ SIPAccount::getVolatileAccountDetails() const
 bool
 SIPAccount::mapPortUPnP()
 {
-    upnp::Mapping map(upnp::PortType::UDP, publishedPort_, localPort_);
+    upnp::Mapping map(upnp::PortType::UDP, config().publishedPort, config().localPort);
     map.setNotifyCallback([w = weak()](upnp::Mapping::sharedPtr_t mapRes) {
         if (auto accPtr = w.lock()) {
             auto oldPort = static_cast<in_port_t>(accPtr->publishedPortUsed_);
             bool success = mapRes->getState() == upnp::MappingState::OPEN
                            or mapRes->getState() == upnp::MappingState::IN_PROGRESS;
-            auto newPort = success ? mapRes->getExternalPort() : accPtr->publishedPort_;
+            auto newPort = success ? mapRes->getExternalPort() : accPtr->config().publishedPort;
             if (not success and not accPtr->isRegistered()) {
                 JAMI_WARN("[Account %s] Failed to open port %u: registering SIP account anyway",
                           accPtr->getAccountID().c_str(),
@@ -800,12 +517,11 @@ SIPAccount::setPushNotificationToken(const std::string& pushDeviceToken)
               getAccountID().c_str(),
               pushDeviceToken.c_str());
 
-    if (deviceKey_ == pushDeviceToken)
+    if (config().deviceKey == pushDeviceToken)
         return;
+    SIPAccountBase::setPushNotificationToken(pushDeviceToken);
 
-    deviceKey_ = pushDeviceToken;
-
-    if (enabled_)
+    if (config().enabled)
         doUnregister([&](bool /* transport_free */) { doRegister(); });
 }
 
@@ -815,7 +531,7 @@ SIPAccount::pushNotificationReceived(const std::string& from,
 {
     JAMI_WARN("[SIP Account %s] pushNotificationReceived: %s", getAccountID().c_str(), from.c_str());
 
-    if (enabled_)
+    if (config().enabled)
         doUnregister([&](bool /* transport_free */) { doRegister(); });
 }
 
@@ -827,7 +543,7 @@ SIPAccount::doRegister()
         return;
     }
 
-    JAMI_DBG("doRegister %s", hostname_.c_str());
+    JAMI_DEBUG("doRegister {:s}", config_->hostname);
 
     /* if UPnP is enabled, then wait for IGD to complete registration */
     if (upnpCtrl_) {
@@ -853,8 +569,8 @@ SIPAccount::doRegister1_()
         }
     }
 
-    link_.resolveSrvName(hasServiceRoute() ? getServiceRoute() : hostname_,
-                         tlsEnable_ ? PJSIP_TRANSPORT_TLS : PJSIP_TRANSPORT_UDP,
+    link_.resolveSrvName(hasServiceRoute() ? getServiceRoute() : config().hostname,
+                         config().tlsEnable ? PJSIP_TRANSPORT_TLS : PJSIP_TRANSPORT_UDP,
                          [w = weak()](std::vector<IpAddr> host_ips) {
                              if (auto acc = w.lock()) {
                                  std::lock_guard<std::recursive_mutex> lock(
@@ -888,11 +604,11 @@ SIPAccount::doRegister2_()
     }
 
     bool ipv6 = bindAddress.isIpv6();
-    transportType_ = tlsEnable_ ? (ipv6 ? PJSIP_TRANSPORT_TLS6 : PJSIP_TRANSPORT_TLS)
-                                : (ipv6 ? PJSIP_TRANSPORT_UDP6 : PJSIP_TRANSPORT_UDP);
+    transportType_ = config().tlsEnable ? (ipv6 ? PJSIP_TRANSPORT_TLS6 : PJSIP_TRANSPORT_TLS)
+                                        : (ipv6 ? PJSIP_TRANSPORT_UDP6 : PJSIP_TRANSPORT_UDP);
 
     // Init TLS settings if the user wants to use TLS
-    if (tlsEnable_) {
+    if (config().tlsEnable) {
         JAMI_DBG("TLS is enabled for account %s", accountID_.c_str());
 
         // Dropping current calls already using the transport is currently required
@@ -912,14 +628,11 @@ SIPAccount::doRegister2_()
         tlsListener_.reset();
     }
 
-    // Init STUN settings for this account if the user selected it
-    initStunConfiguration();
-
     // In our definition of the ip2ip profile (aka Direct IP Calls),
     // no registration should be performed
     if (isIP2IP()) {
         // If we use Tls for IP2IP, transports will be created on connection.
-        if (!tlsEnable_) {
+        if (!config().tlsEnable) {
             setTransport(link_.sipTransportBroker->getUdpTransport(bindAddress));
         }
         setRegistrationState(RegistrationState::REGISTERED);
@@ -932,9 +645,9 @@ SIPAccount::doRegister2_()
         if (isTlsEnabled()) {
             setTransport(link_.sipTransportBroker->getTlsTransport(tlsListener_,
                                                                    hostIp_,
-                                                                   tlsServerName_.empty()
-                                                                       ? hostname_
-                                                                       : tlsServerName_));
+                                                                   config().tlsServerName.empty()
+                                                                       ? config().hostname
+                                                                       : config().tlsServerName));
         } else {
             setTransport(link_.sipTransportBroker->getUdpTransport(bindAddress));
         }
@@ -1000,7 +713,7 @@ SIPAccount::sendRegister()
         return;
     }
 
-    setRegister(true);
+    bRegister_ = true;
     setRegistrationState(RegistrationState::TRYING);
 
     pjsip_regc* regc = nullptr;
@@ -1094,7 +807,7 @@ SIPAccount::setUpTransmissionData(pjsip_tx_data* tdata, long transportKeyType)
 {
     if (hostIp_) {
         auto ai = &tdata->dest_info;
-        ai->name = pj_strdup3(tdata->pool, hostname_.c_str());
+        ai->name = pj_strdup3(tdata->pool, config().hostname.c_str());
         ai->addr.count = 1;
         ai->addr.entry[0].type = (pjsip_transport_type_e) transportKeyType;
         pj_memcpy(&ai->addr.entry[0].addr, hostIp_.pjPtr(), sizeof(pj_sockaddr));
@@ -1149,7 +862,7 @@ SIPAccount::onRegister(pjsip_regc_cbparam* param)
              */
             // update_rfc5626_status(acc, param->rdata);
 
-            if (allowIPAutoRewrite_ and checkNATAddress(param, link_.getPool()))
+            if (config().allowIPAutoRewrite and checkNATAddress(param, link_.getPool()))
                 JAMI_WARN("New contact: %s", getContactHeader().c_str());
 
             /* TODO Check and update Service-Route header */
@@ -1181,10 +894,10 @@ SIPAccount::onRegister(pjsip_regc_cbparam* param)
             scheduleReregistration();
     }
 
-    if (param->expiration != registrationExpire_) {
+    if (param->expiration != config().registrationExpire) {
         JAMI_DBG("Registrar returned EXPIRE value [%u s] different from the requested [%u s]",
                  param->expiration,
-                 registrationExpire_);
+                 config().registrationExpire);
         // NOTE: We don't alter the EXPIRE set by the user even if the registrar
         // returned a different value. PJSIP lib will set the proper timer for
         // the refresh, if the auto-regisration is enabled.
@@ -1200,8 +913,7 @@ SIPAccount::sendUnregister()
         return;
     }
 
-    setRegister(false);
-
+    bRegister_ = false;
     pjsip_regc* regc = getRegistrationInfo();
     if (!regc)
         throw VoipLinkException("Registration structure is NULL");
@@ -1262,7 +974,8 @@ void
 SIPAccount::initTlsConfiguration()
 {
     pjsip_tls_setting_default(&tlsSetting_);
-    tlsSetting_.proto = tlsProtocolFromString(tlsMethod_);
+    const auto& conf = config();
+    tlsSetting_.proto = tlsProtocolFromString(conf.tlsMethod);
 
     // Determine the cipher list supported on this machine
     CipherArray avail_ciphers(256);
@@ -1272,7 +985,7 @@ SIPAccount::initTlsConfiguration()
     avail_ciphers.resize(cipherNum);
 
     ciphers_.clear();
-    std::string_view stream(tlsCiphers_), item;
+    std::string_view stream(conf.tlsCiphers), item;
     while (jami::getline(stream, item, ' ')) {
         std::string cipher(item);
         auto item_cid = pj_ssl_cipher_id(cipher.c_str());
@@ -1295,14 +1008,10 @@ SIPAccount::initTlsConfiguration()
 
     trimCiphers();
 
-    pj_strset(&tlsSetting_.ca_list_file, (char*) tlsCaListFile_.c_str(), tlsCaListFile_.size());
-    pj_strset(&tlsSetting_.cert_file,
-              (char*) tlsCertificateFile_.c_str(),
-              tlsCertificateFile_.size());
-    pj_strset(&tlsSetting_.privkey_file,
-              (char*) tlsPrivateKeyFile_.c_str(),
-              tlsPrivateKeyFile_.size());
-    pj_strset(&tlsSetting_.password, (char*) tlsPassword_.c_str(), tlsPassword_.size());
+    tlsSetting_.ca_list_file = CONST_PJ_STR(conf.tlsCaListFile);
+    tlsSetting_.cert_file = CONST_PJ_STR(conf.tlsCaListFile);
+    tlsSetting_.privkey_file = CONST_PJ_STR(conf.tlsPrivateKeyFile);
+    tlsSetting_.password = CONST_PJ_STR(conf.tlsPassword);
 
     JAMI_DBG("Using %zu ciphers", ciphers_.size());
     tlsSetting_.ciphers_num = ciphers_.size();
@@ -1310,14 +1019,11 @@ SIPAccount::initTlsConfiguration()
         tlsSetting_.ciphers = &ciphers_.front();
     }
 
-    tlsSetting_.verify_server = tlsVerifyServer_;
-    tlsSetting_.verify_client = tlsVerifyClient_;
-    tlsSetting_.require_client_cert = tlsRequireClientCertificate_;
-
-    pjsip_cfg()->endpt.disable_secure_dlg_check = tlsDisableSecureDlgCheck_;
-    JAMI_DEBUG("Secure check dialog disabled ? {}", tlsDisableSecureDlgCheck_);
-
-    tlsSetting_.timeout.sec = atol(tlsNegotiationTimeoutSec_.c_str());
+    tlsSetting_.verify_server = conf.tlsVerifyServer;
+    tlsSetting_.verify_client = conf.tlsVerifyClient;
+    tlsSetting_.require_client_cert = conf.tlsRequireClientCertificate;
+    pjsip_cfg()->endpt.disable_secure_dlg_check = conf.tlsDisableSecureDlgCheck;
+    tlsSetting_.timeout.sec = conf.tlsNegotiationTimeout;
 
     tlsSetting_.qos_type = PJ_QOS_TYPE_BEST_EFFORT;
     tlsSetting_.qos_ignore_error = PJ_TRUE;
@@ -1326,7 +1032,7 @@ SIPAccount::initTlsConfiguration()
 void
 SIPAccount::initStunConfiguration()
 {
-    std::string_view stunServer(stunServer_);
+    std::string_view stunServer(config().stunServer);
     auto pos = stunServer.find(':');
     if (pos == std::string_view::npos) {
         stunServerName_ = sip_utils::CONST_PJ_STR(stunServer);
@@ -1341,11 +1047,11 @@ SIPAccount::initStunConfiguration()
 void
 SIPAccount::loadConfig()
 {
-    if (registrationExpire_ == 0)
-        registrationExpire_
-            = DEFAULT_REGISTRATION_EXPIRE; /** Default expire value for registration */
-
-    if (tlsEnable_) {
+    SIPAccountBase::loadConfig();
+    setCredentials(config().credentials);
+    enablePresence(config().presenceEnabled);
+    initStunConfiguration();
+    if (config().tlsEnable) {
         initTlsConfiguration();
         transportType_ = PJSIP_TRANSPORT_TLS;
     } else
@@ -1361,26 +1067,26 @@ SIPAccount::fullMatch(std::string_view username, std::string_view hostname) cons
 bool
 SIPAccount::userMatch(std::string_view username) const
 {
-    return !username.empty() and username == username_;
+    return !username.empty() and username == config().username;
 }
 
 bool
 SIPAccount::hostnameMatch(std::string_view hostname) const
 {
-    if (hostname == hostname_)
+    if (hostname == config().hostname)
         return true;
     const auto a = ip_utils::getAddrList(hostname);
-    const auto b = ip_utils::getAddrList(hostname_);
+    const auto b = ip_utils::getAddrList(config().hostname);
     return ip_utils::haveCommonAddr(a, b);
 }
 
 bool
 SIPAccount::proxyMatch(std::string_view hostname) const
 {
-    if (hostname == serviceRoute_)
+    if (hostname == config().serviceRoute)
         return true;
     const auto a = ip_utils::getAddrList(hostname);
-    const auto b = ip_utils::getAddrList(hostname_);
+    const auto b = ip_utils::getAddrList(config().hostname);
     return ip_utils::haveCommonAddr(a, b);
 }
 
@@ -1410,8 +1116,9 @@ SIPAccount::getFromUri() const
     std::string transport;
 
     // Get login name if username is not specified
-    std::string username(username_.empty() ? getLoginName() : username_);
-    std::string hostname(hostname_);
+    const auto& conf = config();
+    std::string username(conf.username.empty() ? getLoginName() : conf.username);
+    std::string hostname(conf.hostname);
 
     // UDP does not require the transport specification
     if (transportType_ == PJSIP_TRANSPORT_TLS || transportType_ == PJSIP_TRANSPORT_TLS6) {
@@ -1421,7 +1128,7 @@ SIPAccount::getFromUri() const
         scheme = "sip:";
 
     // Get machine hostname if not provided
-    if (hostname_.empty()) {
+    if (hostname.empty()) {
         hostname = sip_utils::as_view(*pj_gethostname());
     }
 
@@ -1429,8 +1136,8 @@ SIPAccount::getFromUri() const
         hostname = IpAddr(hostname).toString(false, true);
 
     std::string uri = "<" + scheme + username + "@" + hostname + transport + ">";
-    if (not displayName_.empty())
-        return "\"" + displayName_ + "\" " + uri;
+    if (not conf.displayName.empty())
+        return "\"" + conf.displayName + "\" " + uri;
     return uri;
 }
 
@@ -1454,7 +1161,7 @@ SIPAccount::getToUri(const std::string& username) const
 
     // Check if hostname is already specified
     if (username.find('@') == std::string::npos)
-        hostname = hostname_;
+        hostname = config().hostname;
 
     if (not hostname.empty() and IpAddr::isIpv6(hostname))
         hostname = IpAddr(hostname).toString(false, true);
@@ -1481,10 +1188,10 @@ SIPAccount::getServerUri() const
     }
 
     std::string host;
-    if (IpAddr::isIpv6(hostname_))
-        host = IpAddr(hostname_).toString(false, true);
+    if (IpAddr::isIpv6(config().hostname))
+        host = IpAddr(config().hostname).toString(false, true);
     else
-        host = hostname_;
+        host = config().hostname;
 
     return "<" + scheme + host + transport + ">";
 }
@@ -1518,12 +1225,12 @@ SIPAccount::updateContactHeader()
         return;
     }
 
-    auto contactHdr = printContactHeader(username_,
-                                         displayName_,
+    auto contactHdr = printContactHeader(config().username,
+                                         config().displayName,
                                          contactAddress_.toString(false, true),
                                          contactAddress_.getPort(),
                                          PJSIP_TRANSPORT_IS_SECURE(transport_->get()),
-                                         deviceKey_);
+                                         config().deviceKey);
 
     contactHeader_ = std::move(contactHdr);
 }
@@ -1552,18 +1259,22 @@ SIPAccount::initContactAddress()
     pj_uint16_t port;
 
     // Init the address to the local address.
-    link_.findLocalAddressFromTransport(transport_->get(), transportType, hostname_, address, port);
+    link_.findLocalAddressFromTransport(transport_->get(),
+                                        transportType,
+                                        config().hostname,
+                                        address,
+                                        port);
 
     if (getUPnPActive() and getUPnPIpAddress()) {
         address = getUPnPIpAddress().toString();
         port = publishedPortUsed_;
         useUPnPAddressPortInVIA();
         JAMI_DBG("Using UPnP address %s and port %d", address.c_str(), port);
-    } else if (not publishedSameasLocal_) {
+    } else if (not config().publishedSameasLocal) {
         address = getPublishedIpAddress().toString();
-        port = publishedPort_;
+        port = config().publishedPort;
         JAMI_DBG("Using published address %s and port %d", address.c_str(), port);
-    } else if (stunEnabled_) {
+    } else if (config().stunEnabled) {
         auto success = link_.findLocalAddressFromSTUN(transport_->get(),
                                                       &stunServerName_,
                                                       stunPort_,
@@ -1572,7 +1283,7 @@ SIPAccount::initContactAddress()
         if (not success)
             emitSignal<libjami::ConfigurationSignal::StunStatusFailed>(getAccountID());
         setPublishedAddress({address});
-        publishedPort_ = port;
+        publishedPortUsed_ = port;
         usePublishedAddressPortInVIA();
     } else {
         if (!receivedParameter_.empty()) {
@@ -1683,82 +1394,26 @@ SIPAccount::getSupportedTlsProtocols()
 }
 
 void
-SIPAccount::Credentials::computePasswordHash()
-{
-    pj_md5_context pms;
-
-    /* Compute md5 hash = MD5(username ":" realm ":" password) */
-    pj_md5_init(&pms);
-    pj_md5_update(&pms, (const uint8_t*) username.data(), username.length());
-    pj_md5_update(&pms, (const uint8_t*) ":", 1);
-    pj_md5_update(&pms, (const uint8_t*) realm.data(), realm.length());
-    pj_md5_update(&pms, (const uint8_t*) ":", 1);
-    pj_md5_update(&pms, (const uint8_t*) password.data(), password.length());
-
-    unsigned char digest[16];
-    pj_md5_final(&pms, digest);
-
-    char hash[32];
-
-    for (int i = 0; i < 16; ++i)
-        pj_val_to_hex_digit(digest[i], &hash[2 * i]);
-
-    password_h = {hash, 32};
-}
-
-void
-SIPAccount::setCredentials(const std::vector<std::map<std::string, std::string>>& creds)
+SIPAccount::setCredentials(const std::vector<SipAccountConfig::Credentials>& creds)
 {
-    if (creds.empty()) {
-        JAMI_ERR("Cannot authenticate with empty credentials list");
-        return;
-    }
-    credentials_.clear();
     cred_.clear();
-
-    bool md5HashingEnabled = Manager::instance().preferences.getMd5Hash();
-
-    credentials_.reserve(creds.size());
     cred_.reserve(creds.size());
-    for (const auto& cred : creds) {
-        auto realm = cred.find(Conf::CONFIG_ACCOUNT_REALM);
-        auto user = cred.find(Conf::CONFIG_ACCOUNT_USERNAME);
-        auto passw = cred.find(Conf::CONFIG_ACCOUNT_PASSWORD);
-        credentials_.emplace_back(realm != cred.end() ? realm->second : "",
-                                  user != cred.end() ? user->second : "",
-                                  passw != cred.end() ? passw->second : "");
-        auto& c = credentials_.back();
-        if (md5HashingEnabled)
-            c.computePasswordHash();
+    bool md5HashingEnabled = Manager::instance().preferences.getMd5Hash();
 
+    for (auto& c : creds) {
         cred_.emplace_back(
-            pjsip_cred_info {/*.realm     = */ pj_str((char*) c.realm.c_str()),
-                             /*.scheme    = */ pj_str((char*) "digest"),
-                             /*.username  = */ pj_str((char*) c.username.c_str()),
+            pjsip_cred_info {/*.realm     = */ CONST_PJ_STR(c.realm),
+                             /*.scheme    = */ CONST_PJ_STR("digest"),
+                             /*.username  = */ CONST_PJ_STR(c.username),
                              /*.data_type = */
-                             (c.password_h.empty() ? PJSIP_CRED_DATA_PLAIN_PASSWD
-                                                   : PJSIP_CRED_DATA_DIGEST),
+                             (md5HashingEnabled ? PJSIP_CRED_DATA_DIGEST
+                                                : PJSIP_CRED_DATA_PLAIN_PASSWD),
                              /*.data      = */
-                             pj_str((char*) (c.password_h.empty() ? c.password.c_str()
-                                                                  : c.password_h.c_str())),
+                             CONST_PJ_STR(md5HashingEnabled ? c.password_h : c.password),
                              /*.ext       = */ {}});
     }
 }
 
-std::vector<std::map<std::string, std::string>>
-SIPAccount::getCredentials() const
-{
-    std::vector<std::map<std::string, std::string>> ret;
-    ret.reserve(credentials_.size());
-    for (const auto& c : credentials_) {
-        ret.emplace_back(
-            std::map<std::string, std::string> {{Conf::CONFIG_ACCOUNT_REALM, c.realm},
-                                                {Conf::CONFIG_ACCOUNT_USERNAME, c.username},
-                                                {Conf::CONFIG_ACCOUNT_PASSWORD, c.password}});
-    }
-    return ret;
-}
-
 void
 SIPAccount::setRegistrationState(RegistrationState state,
                                  unsigned details_code,
@@ -1767,50 +1422,15 @@ SIPAccount::setRegistrationState(RegistrationState state,
     std::string details_str;
     const pj_str_t* description = pjsip_get_status_text(details_code);
     if (description)
-        details_str = {description->ptr, (size_t) description->slen};
+        details_str = sip_utils::as_view(*description);
     setRegistrationStateDetailed({details_code, details_str});
     SIPAccountBase::setRegistrationState(state, details_code, details_str);
 }
 
-void
-SIPAccount::setRegistrationExpire(unsigned expire)
-{
-    if (expire >= MIN_REGISTRATION_TIME) {
-        JAMI_DBG("Set SIP registration EXPIRE to %u - current %u", expire, registrationExpire_);
-        registrationExpire_ = expire;
-    } else {
-        JAMI_WARN("SIP registration EXPIRE %u is lower than min value %u",
-                  expire,
-                  MIN_REGISTRATION_TIME);
-        registrationExpire_ = MIN_REGISTRATION_TIME;
-    }
-}
-
-std::map<std::string, std::string>
-SIPAccount::getTlsSettings() const
-{
-    return {{Conf::CONFIG_TLS_ENABLE, tlsEnable_ ? TRUE_STR : FALSE_STR},
-            {Conf::CONFIG_TLS_LISTENER_PORT, std::to_string(tlsListenerPort_)},
-            {Conf::CONFIG_TLS_CA_LIST_FILE, tlsCaListFile_},
-            {Conf::CONFIG_TLS_CERTIFICATE_FILE, tlsCertificateFile_},
-            {Conf::CONFIG_TLS_PRIVATE_KEY_FILE, tlsPrivateKeyFile_},
-            {Conf::CONFIG_TLS_PASSWORD, tlsPassword_},
-            {Conf::CONFIG_TLS_METHOD, tlsMethod_},
-            {Conf::CONFIG_TLS_CIPHERS, tlsCiphers_},
-            {Conf::CONFIG_TLS_SERVER_NAME, tlsServerName_},
-            {Conf::CONFIG_TLS_VERIFY_SERVER, tlsVerifyServer_ ? TRUE_STR : FALSE_STR},
-            {Conf::CONFIG_TLS_VERIFY_CLIENT, tlsVerifyClient_ ? TRUE_STR : FALSE_STR},
-            {Conf::CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE,
-             tlsRequireClientCertificate_ ? TRUE_STR : FALSE_STR},
-            {Conf::CONFIG_TLS_DISABLE_SECURE_DLG_CHECK,
-             tlsDisableSecureDlgCheck_ ? TRUE_STR : FALSE_STR},
-            {Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, tlsNegotiationTimeoutSec_}};
-}
-
 bool
 SIPAccount::isIP2IP() const
 {
-    return hostname_.empty();
+    return config().hostname.empty();
 }
 
 SIPPresence*
@@ -1957,8 +1577,6 @@ SIPAccount::checkNATAddress(pjsip_regc_cbparam* param, pj_pool_t* pool)
     }
 
     // Set published Ip address
-    publishedSameasLocal_ = false;
-    publishedIpAddress_ = via_addrstr;
     setPublishedAddress(IpAddr(via_addrstr));
 
     /* Compare received and rport with the URI in our registration */
@@ -2033,12 +1651,12 @@ SIPAccount::checkNATAddress(pjsip_regc_cbparam* param, pj_pool_t* pool)
      * Build new Contact header
      */
     {
-        auto tempContact = printContactHeader(username_,
-                                              displayName_,
+        auto tempContact = printContactHeader(config().username,
+                                              config().displayName,
                                               via_addrstr,
                                               rport,
                                               PJSIP_TRANSPORT_IS_SECURE(tp),
-                                              deviceKey_);
+                                              config().deviceKey);
 
         if (tempContact.empty()) {
             JAMI_ERR("Invalid contact header");
@@ -2134,7 +1752,7 @@ SIPAccount::scheduleReregistration()
 void
 SIPAccount::updateDialogViaSentBy(pjsip_dialog* dlg)
 {
-    if (allowIPAutoRewrite_ && via_addr_.host.slen > 0)
+    if (config().allowIPAutoRewrite && via_addr_.host.slen > 0)
         pjsip_dlg_set_via_sent_by(dlg, &via_addr_, via_tp_);
 }
 
@@ -2324,15 +1942,16 @@ IpAddr
 SIPAccount::createBindingAddress()
 {
     auto family = hostIp_ ? hostIp_.getFamily() : PJ_AF_INET;
+    const auto& conf = config();
 
-    IpAddr ret = bindAddress_.empty()
-                     ? (interface_ == ip_utils::DEFAULT_INTERFACE
+    IpAddr ret = conf.bindAddress.empty()
+                     ? (conf.interface == ip_utils::DEFAULT_INTERFACE
                             ? ip_utils::getAnyHostAddr(family)
                             : ip_utils::getInterfaceAddr(getLocalInterface(), family))
-                     : IpAddr(bindAddress_, family);
+                     : IpAddr(conf.bindAddress, family);
 
     if (ret.getPort() == 0) {
-        ret.setPort(tlsEnable_ ? getTlsListenerPort() : getLocalPort());
+        ret.setPort(conf.tlsEnable ? conf.tlsListenerPort : conf.localPort);
     }
 
     return ret;
@@ -2350,6 +1969,7 @@ SIPAccount::setActiveCodecs(const std::vector<unsigned>& list)
         JAMI_WARN("All video codecs disabled, enabling all");
         setAllCodecsActive(MEDIA_VIDEO, true);
     }
+    config_->activeCodecs = getActiveCodecs(MEDIA_ALL);
 }
 
 } // namespace jami
diff --git a/src/sip/sipaccount.h b/src/sip/sipaccount.h
index 6b4ddf04b4c901daa22442239cdb1f40791a53f1..4c3adb950cda84760ca9d3da1e6230382b9ebf66 100644
--- a/src/sip/sipaccount.h
+++ b/src/sip/sipaccount.h
@@ -31,6 +31,7 @@
 #include "sip/siptransport.h"
 #include "noncopyable.h"
 #include "ring_types.h" // enable_if_base_of
+#include "sipaccount_config.h"
 
 #include <pjsip/sip_transport_tls.h>
 #include <pjsip/sip_types.h>
@@ -39,23 +40,8 @@
 #include <vector>
 #include <map>
 
-namespace YAML {
-class Node;
-class Emitter;
-} // namespace YAML
-
 namespace jami {
 
-namespace Conf {
-const char* const KEEP_ALIVE_ENABLED = "keepAlive";
-
-// TODO: write an object to store credential which implement serializable
-const char* const SRTP_KEY = "srtp";
-const char* const SRTP_ENABLE_KEY = "enable";
-const char* const KEY_EXCHANGE_KEY = "keyExchange";
-const char* const RTP_FALLBACK_KEY = "rtpFallback";
-} // namespace Conf
-
 typedef std::vector<pj_ssl_cipher> CipherArray;
 
 class SIPPresence;
@@ -68,7 +54,7 @@ class SIPCall;
 class SIPAccount : public SIPAccountBase
 {
 public:
-    constexpr static const char* const ACCOUNT_TYPE = "SIP";
+    constexpr static auto ACCOUNT_TYPE = ACCOUNT_TYPE_SIP;
     constexpr static const char* const PN_FCM = "fcm";
     constexpr static const char* const PN_APNS = "apns";
 
@@ -97,6 +83,19 @@ public:
 
     ~SIPAccount() noexcept;
 
+    const SipAccountConfig& config() const {
+        return *static_cast<const SipAccountConfig*>(&Account::config());
+    }
+
+    std::unique_ptr<AccountConfig> buildConfig() const override {
+        return std::make_unique<SipAccountConfig>(getAccountID());
+    }
+    inline void editConfig(std::function<void(SipAccountConfig& conf)>&& edit) {
+        Account::editConfig([&](AccountConfig& conf) {
+            edit(*static_cast<SipAccountConfig*>(&conf));
+        });
+    }
+
     const char* getAccountType() const override { return ACCOUNT_TYPE; }
 
     pjsip_host_port getHostPortFromSTUN(pj_pool_t* pool);
@@ -123,25 +122,6 @@ public:
      */
     bool isIP2IP() const override;
 
-    /**
-     * Serialize internal state of this account for configuration
-     * @param out Emitter to which state will be saved
-     */
-    virtual void serialize(YAML::Emitter& out) const override;
-
-    /**
-     * Populate the internal state for this account based on info stored in the configuration file
-     * @param The configuration node for this account
-     */
-    virtual void unserialize(const YAML::Node& node) override;
-
-    /**
-     * Return an map containing the internal state of this account. Client application can use this
-     * method to manage account info.
-     * @return A map containing the account information.
-     */
-    virtual std::map<std::string, std::string> getAccountDetails() const override;
-
     /**
      * Retrieve volatile details such as recent registration errors
      * @return std::map< std::string, std::string > The account volatile details
@@ -188,13 +168,13 @@ public:
      * @param none
      * @return int The number of credentials set for this account.
      */
-    unsigned getCredentialCount() const { return credentials_.size(); }
+    unsigned getCredentialCount() const { return config().credentials.size(); }
 
-    bool hasCredentials() const { return not credentials_.empty(); }
+    bool hasCredentials() const { return not config().credentials.empty(); }
 
-    void setCredentials(const std::vector<std::map<std::string, std::string>>& details);
-
-    std::vector<std::map<std::string, std::string>> getCredentials() const;
+    std::vector<std::map<std::string, std::string>> getCredentials() const {
+        return config().getCredentials();
+    }
 
     virtual void setRegistrationState(RegistrationState state,
                                       unsigned code = 0,
@@ -209,28 +189,15 @@ public:
      */
     unsigned getRegistrationExpire() const
     {
-        if (registrationExpire_ == 0)
-            return PJSIP_REGC_EXPIRATION_NOT_SPECIFIED;
-
-        return registrationExpire_;
+        unsigned re = config().registrationExpire;
+        return re ? re : PJSIP_REGC_EXPIRATION_NOT_SPECIFIED;
     }
 
-    /**
-     * Set the expiration for this account as found in
-     * the "Expire" sip header or the CONTACT's "expire" param.
-     */
-    void setRegistrationExpire(unsigned expire);
-
     /**
      * Registration flag
      */
     bool isRegistered() const { return bRegister_; }
 
-    /**
-     * Set registration flag
-     */
-    void setRegister(bool result) { bRegister_ = result; }
-
     /**
      * Get the registration structure that is used
      * for PJSIP in the registration process.
@@ -259,27 +226,13 @@ public:
      * actually using.
      * @return pj_uint16 The port used for that account
      */
-    pj_uint16_t getLocalPort() const { return localPort_; }
-
-    /**
-     * Set the new port on which this account is running over.
-     * @pram port The port used by this account.
-     */
-    void setLocalPort(pj_uint16_t port) { localPort_ = port; }
+    uint16_t getLocalPort() const { return config().localPort; }
 
-    /**
-     * Get the bind ip address on which the account should use, or is
-     * actually using.
-     * Note: if it is NULL, this address should not be used
-     * @return std::string The bind ip address used for that account
-     */
-    std::string getBindAddress() const { return bindAddress_; }
-
-    /**
-     * Set the new bind ip address on which this account is bind on.
-     * @pram address The bind ip address used by this account.
-     */
-    void setBindAddress(const std::string& address) { bindAddress_ = address; }
+    void setLocalPort(uint16_t port) {
+        editConfig([&](SipAccountConfig& config){
+            config.localPort = port;
+        });
+    }
 
     /**
      * @return pjsip_tls_setting structure, filled from the configuration
@@ -292,9 +245,9 @@ public:
      * Get the local port for TLS listener.
      * @return pj_uint16 The port used for that account
      */
-    pj_uint16_t getTlsListenerPort() const { return tlsListenerPort_; }
+    pj_uint16_t getTlsListenerPort() const { return config().tlsListenerPort; }
 
-    pj_str_t getStunServerName() const override { return stunServerName_; }
+    pj_str_t getStunServerName() const { return stunServerName_; }
 
     static const std::vector<std::string>& getSupportedTlsCiphers();
     static const std::vector<std::string>& getSupportedTlsProtocols();
@@ -310,7 +263,17 @@ public:
      * @return bool Tells if current transport for that
      * account is set to OTHER.
      */
-    bool isStunEnabled() const override { return stunEnabled_; }
+    bool isStunEnabled() const override { return config().stunEnabled; }
+
+    /**
+     * @return pj_str_t , filled from the configuration
+     * file, that can be used directly by PJSIP to initialize
+     * an alternate UDP transport.
+     */
+    std::string getStunServer() const
+    {
+        return config().stunServer;
+    }
 
     /**
      * @return pj_str_t "From" uri based on account information.
@@ -354,13 +317,13 @@ public:
      */
     std::string getContactHeader() const;
 
-    std::string getServiceRoute() const { return serviceRoute_; }
+    std::string getServiceRoute() const { return config().serviceRoute; }
 
-    bool hasServiceRoute() const { return not serviceRoute_.empty(); }
+    bool hasServiceRoute() const { return not config().serviceRoute.empty(); }
 
-    virtual bool isTlsEnabled() const override { return tlsEnable_; }
+    virtual bool isTlsEnabled() const override { return config().tlsEnable; }
 
-    virtual bool getSrtpFallback() const override { return srtpFallback_; }
+    virtual bool getSrtpFallback() const override { return config().srtpFallback; }
 
     void setReceivedParameter(const std::string& received)
     {
@@ -375,7 +338,7 @@ public:
     int getRPort() const
     {
         if (rPort_ == -1)
-            return localPort_;
+            return config().localPort;
         else
             return rPort_;
     }
@@ -386,7 +349,7 @@ public:
         via_addr_.port = rPort;
     }
 
-    bool isRegistrationRefreshEnabled() const { return registrationRefreshEnabled_; }
+    bool isRegistrationRefreshEnabled() const { return config().registrationRefreshEnabled; }
 
     void setTransport(const std::shared_ptr<SipTransport>& = nullptr);
 
@@ -459,7 +422,7 @@ public:
     IpAddr createBindingAddress();
 
     void setActiveCodecs(const std::vector<unsigned>& list) override;
-    bool isSrtpEnabled() const override { return srtpKeyExchange_ != KeyExchangeProtocol::NONE; }
+    bool isSrtpEnabled() const override { return config().srtpKeyExchange != KeyExchangeProtocol::NONE; }
 
     void setPushNotificationToken(const std::string& pushDeviceToken = "") override;
 
@@ -478,14 +441,9 @@ private:
     bool initContactAddress();
     void updateContactHeader();
 
-    void setUpTransmissionData(pjsip_tx_data* tdata, long transportKeyType);
+    void setCredentials(const std::vector<SipAccountConfig::Credentials>& creds);
 
-    /**
-     * Set the internal state for this account, mainly used to manage account details from the
-     * client application.
-     * @param The map containing the account information.
-     */
-    void setAccountDetails(const std::map<std::string, std::string>& details) override;
+    void setUpTransmissionData(pjsip_tx_data* tdata, long transportKeyType);
 
     NON_COPYABLE(SIPAccount);
 
@@ -514,10 +472,10 @@ private:
 
     struct
     {
-        pj_bool_t active;     /**< Flag of reregister status. */
-        pj_timer_entry timer; /**< Timer for reregistration.  */
-        unsigned attempt_cnt; /**< Attempt counter.     */
-    } auto_rereg_;            /**< Reregister/reconnect data. */
+        pj_bool_t active {false};     /**< Flag of reregister status. */
+        pj_timer_entry timer {}; /**< Timer for reregistration.  */
+        unsigned attempt_cnt {0}; /**< Attempt counter.     */
+    } auto_rereg_ {};            /**< Reregister/reconnect data. */
 
     std::uniform_int_distribution<int> delay10ZeroDist_ {-10000, 10000};
     std::uniform_int_distribution<unsigned int> delay10PosDist_ {0, 10000};
@@ -525,24 +483,6 @@ private:
     void scheduleReregistration();
     void autoReregTimerCb();
 
-    /**
-     * Map of credential for this account
-     */
-    struct Credentials
-    {
-        std::string realm {};
-        std::string username {};
-        std::string password {};
-        std::string password_h {};
-        Credentials(const std::string& r, const std::string& u, const std::string& p)
-            : realm(r)
-            , username(u)
-            , password(p)
-        {}
-        void computePasswordHash();
-    };
-    std::vector<Credentials> credentials_;
-
     std::shared_ptr<SipTransport> transport_ {};
 
     std::shared_ptr<TlsListener> tlsListener_ {};
@@ -610,23 +550,13 @@ private:
     /**
      * The pjsip client registration information
      */
-    pjsip_regc* regc_;
+    pjsip_regc* regc_ {nullptr};
 
     /**
      * To check if the account is registered
      */
     bool bRegister_;
 
-    /**
-     * Network settings
-     */
-    unsigned registrationExpire_;
-
-    /**
-     * Input Outbound Proxy Server Address
-     */
-    std::string serviceRoute_;
-
     /**
      * Credential information stored for further registration.
      * Points to credentials_ members.
@@ -653,62 +583,17 @@ private:
      */
     pj_uint16_t stunPort_ {PJ_STUN_PORT};
 
-    /**
-     * Local port to whih this account is bound
-     */
-    pj_uint16_t localPort_ {sip_utils::DEFAULT_AUTO_SELECT_PORT};
-
-    /**
-     * Potential ip addresss on which this account is bound
-     */
-    std::string bindAddress_;
-
-    /**
-     * The TLS listener port
-     */
-    pj_uint16_t tlsListenerPort_ {sip_utils::DEFAULT_AUTO_SELECT_PORT};
-
     /**
      * Send Request Callback
      */
     static void onComplete(void* token, pjsip_event* event);
 
-    bool tlsEnable_ {false};
-    std::string tlsMethod_;
-    std::string tlsCiphers_;
-    std::string tlsServerName_;
-    bool tlsVerifyServer_;
-    bool tlsVerifyClient_;
-    bool tlsRequireClientCertificate_;
-    bool tlsDisableSecureDlgCheck_;
-    std::string tlsNegotiationTimeoutSec_;
-
-    /**
-     * Specifies the type of key exchange used for SRTP, if any.
-     * This only determine if the media channel is secured.
-     */
-    KeyExchangeProtocol srtpKeyExchange_ {KeyExchangeProtocol::NONE};
-
-    /**
-     * Determine if the softphone should fallback on non secured media channel if SRTP negotiation
-     * fails. Make sure other SIP endpoints share the same behavior since it could result in
-     * encrypted data to be played through the audio device.
-     */
-    bool srtpFallback_ {};
-
     /**
      * Details about the registration state.
      * This is a protocol Code:Description pair.
      */
     std::pair<int, std::string> registrationStateDetailed_;
 
-    /**
-     * Enable/disable automatic refresh of the registration.
-     * If enabled, a new registration request is sent shortly before
-     * the current registration expires.
-     */
-    bool registrationRefreshEnabled_;
-
     /**
      * Optional: "received" parameter from VIA header
      */
@@ -717,7 +602,7 @@ private:
     /**
      * Optional: "rport" parameter from VIA header
      */
-    int rPort_;
+    int rPort_ {-1};
 
     /**
      * Optional: via_addr construct from received parameters
@@ -738,10 +623,7 @@ private:
     std::string contactHeader_;
     // Contact address (the address part of a SIP URI)
     IpAddr contactAddress_ {};
-    // If true, the contact addreass and header will be rewritten
-    // using the information received from the registrar.
-    bool allowIPAutoRewrite_;
-    pjsip_transport* via_tp_;
+    pjsip_transport* via_tp_ {nullptr};
 
     /**
      * Presence data structure
diff --git a/src/sip/sipaccount_config.cpp b/src/sip/sipaccount_config.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ebf03f7459ff58b11a1f4d0454c99ece104b4783
--- /dev/null
+++ b/src/sip/sipaccount_config.cpp
@@ -0,0 +1,361 @@
+/*
+ *  Copyright (C) 2004-2022 Savoir-faire Linux Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+#include "sipaccount_config.h"
+#include "account_const.h"
+#include "account_schema.h"
+#include "config/yamlparser.h"
+
+extern "C" {
+#include <pjlib-util/md5.h>
+}
+
+namespace jami {
+
+namespace Conf {
+constexpr const char* ID_KEY = "id";
+constexpr const char* USERNAME_KEY = "username";
+constexpr const char* BIND_ADDRESS_KEY = "bindAddress";
+constexpr const char* INTERFACE_KEY = "interface";
+constexpr const char* PORT_KEY = "port";
+constexpr const char* PUBLISH_ADDR_KEY = "publishAddr";
+constexpr const char* PUBLISH_PORT_KEY = "publishPort";
+constexpr const char* SAME_AS_LOCAL_KEY = "sameasLocal";
+constexpr const char* DTMF_TYPE_KEY = "dtmfType";
+constexpr const char* SERVICE_ROUTE_KEY = "serviceRoute";
+constexpr const char* ALLOW_IP_AUTO_REWRITE = "allowIPAutoRewrite";
+constexpr const char* PRESENCE_ENABLED_KEY = "presenceEnabled";
+constexpr const char* PRESENCE_PUBLISH_SUPPORTED_KEY = "presencePublishSupported";
+constexpr const char* PRESENCE_SUBSCRIBE_SUPPORTED_KEY = "presenceSubscribeSupported";
+constexpr const char* PRESENCE_STATUS_KEY = "presenceStatus";
+constexpr const char* PRESENCE_NOTE_KEY = "presenceNote";
+constexpr const char* PRESENCE_MODULE_ENABLED_KEY = "presenceModuleEnabled";
+constexpr const char* KEEP_ALIVE_ENABLED = "keepAlive";
+
+constexpr const char* const TLS_KEY = "tls";
+constexpr const char* CERTIFICATE_KEY = "certificate";
+constexpr const char* CALIST_KEY = "calist";
+constexpr const char* TLS_PORT_KEY = "tlsPort";
+constexpr const char* CIPHERS_KEY = "ciphers";
+constexpr const char* TLS_ENABLE_KEY = "enable";
+constexpr const char* METHOD_KEY = "method";
+constexpr const char* TIMEOUT_KEY = "timeout";
+constexpr const char* TLS_PASSWORD_KEY = "password";
+constexpr const char* PRIVATE_KEY_KEY = "privateKey";
+constexpr const char* REQUIRE_CERTIF_KEY = "requireCertif";
+constexpr const char* SERVER_KEY = "server";
+constexpr const char* VERIFY_CLIENT_KEY = "verifyClient";
+constexpr const char* VERIFY_SERVER_KEY = "verifyServer";
+constexpr const char* DISABLE_SECURE_DLG_CHECK = "keepAlive";
+
+constexpr const char* STUN_ENABLED_KEY = "stunEnabled";
+constexpr const char* STUN_SERVER_KEY = "stunServer";
+constexpr const char* CRED_KEY = "credential";
+constexpr const char* SRTP_KEY = "srtp";
+constexpr const char* SRTP_ENABLE_KEY = "enable";
+constexpr const char* KEY_EXCHANGE_KEY = "keyExchange";
+constexpr const char* RTP_FALLBACK_KEY = "rtpFallback";
+} // namespace Conf
+
+static constexpr unsigned MIN_REGISTRATION_TIME = 60;                  // seconds
+
+using yaml_utils::parseValueOptional;
+using yaml_utils::parseVectorMap;
+
+static void
+addRangeToDetails(std::map<std::string, std::string>& a,
+                  const char* minKey,
+                  const char* maxKey,
+                  const std::pair<uint16_t, uint16_t>& range)
+{
+    a.emplace(minKey, std::to_string(range.first));
+    a.emplace(maxKey, std::to_string(range.second));
+}
+
+void
+SipAccountConfig::serialize(YAML::Emitter& out) const
+{
+    out << YAML::BeginMap;
+    out << YAML::Key << Conf::ID_KEY << YAML::Value << id;
+    SipAccountBaseConfig::serialize(out);
+
+    out << YAML::Key << Conf::BIND_ADDRESS_KEY << YAML::Value << bindAddress;
+    out << YAML::Key << Conf::PORT_KEY << YAML::Value << localPort;
+    out << YAML::Key << Conf::PUBLISH_PORT_KEY << YAML::Value << publishedPort;
+
+    out << YAML::Key << Conf::USERNAME_KEY << YAML::Value << username;
+
+    // each credential is a map, and we can have multiple credentials
+    out << YAML::Key << Conf::CRED_KEY << YAML::Value << getCredentials();
+
+    out << YAML::Key << Conf::KEEP_ALIVE_ENABLED << YAML::Value << registrationRefreshEnabled;
+
+    //out << YAML::Key << PRESENCE_MODULE_ENABLED_KEY << YAML::Value
+    //    << (presence_ and presence_->isEnabled());
+
+    out << YAML::Key << Conf::CONFIG_ACCOUNT_REGISTRATION_EXPIRE << YAML::Value
+        << registrationExpire;
+    out << YAML::Key << Conf::SERVICE_ROUTE_KEY << YAML::Value << serviceRoute;
+    out << YAML::Key << Conf::ALLOW_IP_AUTO_REWRITE << YAML::Value << allowIPAutoRewrite;
+    out << YAML::Key << Conf::STUN_ENABLED_KEY << YAML::Value << stunEnabled;
+    out << YAML::Key << Conf::STUN_SERVER_KEY << YAML::Value << stunServer;
+
+    // tls submap
+    out << YAML::Key << Conf::TLS_KEY << YAML::Value << YAML::BeginMap;
+    out << YAML::Key << Conf::CALIST_KEY << YAML::Value << tlsCaListFile;
+    out << YAML::Key << Conf::CERTIFICATE_KEY << YAML::Value << tlsCertificateFile;
+    out << YAML::Key << Conf::TLS_PASSWORD_KEY << YAML::Value << tlsPassword;
+    out << YAML::Key << Conf::PRIVATE_KEY_KEY << YAML::Value << tlsPrivateKeyFile;
+    out << YAML::Key << Conf::TLS_ENABLE_KEY << YAML::Value << tlsEnable;
+    out << YAML::Key << Conf::TLS_PORT_KEY << YAML::Value << tlsListenerPort;
+    out << YAML::Key << Conf::VERIFY_CLIENT_KEY << YAML::Value << tlsVerifyClient;
+    out << YAML::Key << Conf::VERIFY_SERVER_KEY << YAML::Value << tlsVerifyServer;
+    out << YAML::Key << Conf::REQUIRE_CERTIF_KEY << YAML::Value << tlsRequireClientCertificate;
+    out << YAML::Key << Conf::DISABLE_SECURE_DLG_CHECK << YAML::Value << tlsDisableSecureDlgCheck;
+    out << YAML::Key << Conf::TIMEOUT_KEY << YAML::Value << tlsNegotiationTimeout;
+    out << YAML::Key << Conf::CIPHERS_KEY << YAML::Value << tlsCiphers;
+    out << YAML::Key << Conf::METHOD_KEY << YAML::Value << tlsMethod;
+    out << YAML::Key << Conf::SERVER_KEY << YAML::Value << tlsServerName;
+    out << YAML::EndMap;
+
+    // srtp submap
+    out << YAML::Key << Conf::SRTP_KEY << YAML::Value << YAML::BeginMap;
+    out << YAML::Key << Conf::KEY_EXCHANGE_KEY << YAML::Value
+        << sip_utils::getKeyExchangeName(srtpKeyExchange);
+    out << YAML::Key << Conf::RTP_FALLBACK_KEY << YAML::Value << srtpFallback;
+    out << YAML::EndMap;
+
+    out << YAML::EndMap;
+}
+
+void
+SipAccountConfig::unserialize(const YAML::Node& node)
+{
+    SipAccountBaseConfig::unserialize(node);
+    parseValueOptional(node, Conf::USERNAME_KEY, username);
+    parseValueOptional(node, Conf::BIND_ADDRESS_KEY, bindAddress);
+    parseValueOptional(node, Conf::PORT_KEY, localPort);
+    parseValueOptional(node, Conf::PUBLISH_PORT_KEY, publishedPort);
+    parseValueOptional(node, Conf::CONFIG_ACCOUNT_REGISTRATION_EXPIRE, registrationExpire);
+    registrationExpire = std::max(MIN_REGISTRATION_TIME, registrationExpire);
+    parseValueOptional(node, Conf::KEEP_ALIVE_ENABLED, registrationRefreshEnabled);
+    parseValueOptional(node, Conf::SERVICE_ROUTE_KEY, serviceRoute);
+    parseValueOptional(node, Conf::ALLOW_IP_AUTO_REWRITE, allowIPAutoRewrite);
+
+    parseValueOptional(node, Conf::PRESENCE_MODULE_ENABLED_KEY, presenceEnabled);
+    parseValueOptional(node, Conf::PRESENCE_PUBLISH_SUPPORTED_KEY, publishSupported);
+    parseValueOptional(node, Conf::PRESENCE_SUBSCRIBE_SUPPORTED_KEY, subscribeSupported);
+
+    // ICE - STUN/TURN
+    parseValueOptional(node, Conf::STUN_ENABLED_KEY, stunEnabled);
+    parseValueOptional(node, Conf::STUN_SERVER_KEY, stunServer);
+
+    const auto& credsNode = node[Conf::CRED_KEY];
+    setCredentials(parseVectorMap(credsNode,
+                                  {Conf::CONFIG_ACCOUNT_REALM,
+                                   Conf::CONFIG_ACCOUNT_USERNAME,
+                                   Conf::CONFIG_ACCOUNT_PASSWORD}));
+
+    // get tls submap
+    try {
+        const auto& tlsMap = node[Conf::TLS_KEY];
+        parseValueOptional(tlsMap, Conf::CERTIFICATE_KEY, tlsCertificateFile);
+        parseValueOptional(tlsMap, Conf::CALIST_KEY, tlsCaListFile);
+        parseValueOptional(tlsMap, Conf::TLS_PASSWORD_KEY, tlsPassword);
+        parseValueOptional(tlsMap, Conf::PRIVATE_KEY_KEY, tlsPrivateKeyFile);
+        parseValueOptional(tlsMap, Conf::TLS_ENABLE_KEY, tlsEnable);
+        parseValueOptional(tlsMap, Conf::TLS_PORT_KEY, tlsListenerPort);
+        parseValueOptional(tlsMap, Conf::CIPHERS_KEY, tlsCiphers);
+        parseValueOptional(tlsMap, Conf::METHOD_KEY, tlsMethod);
+        parseValueOptional(tlsMap, Conf::SERVER_KEY, tlsServerName);
+        parseValueOptional(tlsMap, Conf::REQUIRE_CERTIF_KEY, tlsRequireClientCertificate);
+        parseValueOptional(tlsMap, Conf::VERIFY_CLIENT_KEY, tlsVerifyClient);
+        parseValueOptional(tlsMap, Conf::VERIFY_SERVER_KEY, tlsVerifyServer);
+        parseValueOptional(tlsMap, Conf::DISABLE_SECURE_DLG_CHECK, tlsDisableSecureDlgCheck);
+        parseValueOptional(tlsMap, Conf::TIMEOUT_KEY, tlsNegotiationTimeout);
+    } catch (...) {}
+
+    // get srtp submap
+    const auto& srtpMap = node[Conf::SRTP_KEY];
+    std::string tmpKey;
+    parseValueOptional(srtpMap, Conf::KEY_EXCHANGE_KEY, tmpKey);
+    srtpKeyExchange = sip_utils::getKeyExchangeProtocol(tmpKey);
+    parseValueOptional(srtpMap, Conf::RTP_FALLBACK_KEY, srtpFallback);
+}
+
+std::map<std::string, std::string>
+SipAccountConfig::toMap() const
+{
+    auto a = SipAccountBaseConfig::toMap();
+    a.emplace(Conf::CONFIG_ACCOUNT_USERNAME, username);
+    a.emplace(Conf::CONFIG_LOCAL_PORT, std::to_string(localPort));
+    a.emplace(Conf::CONFIG_ACCOUNT_DTMF_TYPE, dtmfType);
+    a.emplace(Conf::CONFIG_LOCAL_INTERFACE, interface);
+    a.emplace(Conf::CONFIG_PUBLISHED_PORT, std::to_string(publishedPort));
+    a.emplace(Conf::CONFIG_PUBLISHED_SAMEAS_LOCAL, publishedSameasLocal ? TRUE_STR : FALSE_STR);
+    a.emplace(Conf::CONFIG_PUBLISHED_ADDRESS, publishedIp);
+    a.emplace(Conf::CONFIG_STUN_ENABLE, stunEnabled ? TRUE_STR : FALSE_STR);
+    a.emplace(Conf::CONFIG_STUN_SERVER, stunServer);
+
+    std::string password {};
+    if (not credentials.empty()) {
+        for (const auto& cred : credentials)
+            if (cred.username == username) {
+                password = cred.password;
+                break;
+            }
+    }
+    a.emplace(Conf::CONFIG_ACCOUNT_PASSWORD, std::move(password));
+
+    a.emplace(Conf::CONFIG_TLS_ENABLE, tlsEnable ? TRUE_STR : FALSE_STR);
+    a.emplace(Conf::CONFIG_TLS_LISTENER_PORT, std::to_string(tlsListenerPort));
+    a.emplace(Conf::CONFIG_TLS_CA_LIST_FILE, tlsCaListFile);
+    a.emplace(Conf::CONFIG_TLS_CERTIFICATE_FILE, tlsCertificateFile);
+    a.emplace(Conf::CONFIG_TLS_PRIVATE_KEY_FILE, tlsPrivateKeyFile);
+    a.emplace(Conf::CONFIG_TLS_PASSWORD, tlsPassword);
+    a.emplace(Conf::CONFIG_TLS_METHOD, tlsMethod);
+    a.emplace(Conf::CONFIG_TLS_CIPHERS, tlsCiphers);
+    a.emplace(Conf::CONFIG_TLS_SERVER_NAME, tlsServerName);
+    a.emplace(Conf::CONFIG_TLS_VERIFY_SERVER, tlsVerifyServer ? TRUE_STR : FALSE_STR);
+    a.emplace(Conf::CONFIG_TLS_VERIFY_CLIENT, tlsVerifyClient ? TRUE_STR : FALSE_STR);
+    a.emplace(Conf::CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, tlsRequireClientCertificate ? TRUE_STR : FALSE_STR);
+    a.emplace(Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, std::to_string(tlsNegotiationTimeout));
+    a.emplace(Conf::CONFIG_TLS_DISABLE_SECURE_DLG_CHECK, tlsDisableSecureDlgCheck ? TRUE_STR : FALSE_STR);
+    return a;
+}
+
+void
+SipAccountConfig::fromMap(const std::map<std::string, std::string>& details)
+{
+    SipAccountBaseConfig::fromMap(details);
+
+    // general sip settings
+    parseString(details, Conf::CONFIG_ACCOUNT_USERNAME, username);
+    parseInt(details, Conf::CONFIG_LOCAL_PORT, localPort);
+    parseString(details, Conf::CONFIG_BIND_ADDRESS, bindAddress);
+    parseString(details, Conf::CONFIG_ACCOUNT_ROUTESET, serviceRoute);
+    parseBool(details, Conf::CONFIG_ACCOUNT_IP_AUTO_REWRITE, allowIPAutoRewrite);
+    parseString(details, Conf::CONFIG_LOCAL_INTERFACE, interface);
+    parseBool(details, Conf::CONFIG_PUBLISHED_SAMEAS_LOCAL, publishedSameasLocal);
+    parseString(details, Conf::CONFIG_PUBLISHED_ADDRESS, publishedIp);
+    parseInt(details, Conf::CONFIG_PUBLISHED_PORT, publishedPort);
+    parseBool(details, Conf::CONFIG_PRESENCE_ENABLED, presenceEnabled);
+    parseString(details, Conf::CONFIG_ACCOUNT_DTMF_TYPE, dtmfType);
+
+    int tmpMin = -1;
+    parseInt(details, Conf::CONFIG_ACCOUNT_AUDIO_PORT_MIN, tmpMin);
+    int tmpMax = -1;
+    parseInt(details, Conf::CONFIG_ACCOUNT_AUDIO_PORT_MAX, tmpMax);
+    updateRange(tmpMin, tmpMax, audioPortRange);
+    tmpMin = -1;
+    parseInt(details, Conf::CONFIG_ACCOUNT_VIDEO_PORT_MIN, tmpMin);
+    tmpMax = -1;
+    parseInt(details, Conf::CONFIG_ACCOUNT_VIDEO_PORT_MAX, tmpMax);
+    updateRange(tmpMin, tmpMax, videoPortRange);
+
+    if (credentials.empty()) { // credentials not set, construct 1 entry
+        JAMI_WARN("No credentials set, inferring them...");
+        std::map<std::string, std::string> map;
+        map[Conf::CONFIG_ACCOUNT_USERNAME] = username;
+        parseString(details, Conf::CONFIG_ACCOUNT_PASSWORD, map[Conf::CONFIG_ACCOUNT_PASSWORD]);
+        map[Conf::CONFIG_ACCOUNT_REALM] = "*";
+        setCredentials({map});
+    }
+
+    // ICE - STUN
+    parseBool(details, Conf::CONFIG_STUN_ENABLE, stunEnabled);
+    parseString(details, Conf::CONFIG_STUN_SERVER, stunServer);
+
+    // TLS
+    parseBool(details, Conf::CONFIG_TLS_ENABLE, tlsEnable);
+    parseInt(details, Conf::CONFIG_TLS_LISTENER_PORT, tlsListenerPort);
+    parsePath(details, Conf::CONFIG_TLS_CA_LIST_FILE, tlsCaListFile, path);
+    parsePath(details, Conf::CONFIG_TLS_CERTIFICATE_FILE, tlsCertificateFile, path);
+    parsePath(details, Conf::CONFIG_TLS_PRIVATE_KEY_FILE, tlsPrivateKeyFile, path);
+    parseString(details, Conf::CONFIG_TLS_PASSWORD, tlsPassword);
+    parseString(details, Conf::CONFIG_TLS_METHOD, tlsMethod);
+    parseString(details, Conf::CONFIG_TLS_CIPHERS, tlsCiphers);
+    parseString(details, Conf::CONFIG_TLS_SERVER_NAME, tlsServerName);
+    parseBool(details, Conf::CONFIG_TLS_VERIFY_SERVER, tlsVerifyServer);
+    parseBool(details, Conf::CONFIG_TLS_VERIFY_CLIENT, tlsVerifyClient);
+    parseBool(details, Conf::CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, tlsRequireClientCertificate);
+    parseBool(details, Conf::CONFIG_TLS_DISABLE_SECURE_DLG_CHECK, tlsDisableSecureDlgCheck);
+    parseInt(details, Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, tlsNegotiationTimeout);
+}
+
+SipAccountConfig::Credentials::Credentials(const std::map<std::string, std::string>& cred)
+{
+    auto itrealm = cred.find(Conf::CONFIG_ACCOUNT_REALM);
+    auto user = cred.find(Conf::CONFIG_ACCOUNT_USERNAME);
+    auto passw = cred.find(Conf::CONFIG_ACCOUNT_PASSWORD);
+    realm = itrealm != cred.end() ? itrealm->second : "";
+    username = user != cred.end() ? user->second : "";
+    password = passw != cred.end() ? passw->second : "";
+    computePasswordHash();
+}
+
+std::map<std::string, std::string>
+SipAccountConfig::Credentials::toMap() const
+{
+    return {{Conf::CONFIG_ACCOUNT_REALM, realm},
+            {Conf::CONFIG_ACCOUNT_USERNAME, username},
+            {Conf::CONFIG_ACCOUNT_PASSWORD, password}};
+}
+
+void
+SipAccountConfig::Credentials::computePasswordHash() {
+    pj_md5_context pms;
+
+    /* Compute md5 hash = MD5(username ":" realm ":" password) */
+    pj_md5_init(&pms);
+    pj_md5_update(&pms, (const uint8_t*) username.data(), username.length());
+    pj_md5_update(&pms, (const uint8_t*) ":", 1);
+    pj_md5_update(&pms, (const uint8_t*) realm.data(), realm.length());
+    pj_md5_update(&pms, (const uint8_t*) ":", 1);
+    pj_md5_update(&pms, (const uint8_t*) password.data(), password.length());
+
+    unsigned char digest[16];
+    pj_md5_final(&pms, digest);
+
+    char hash[32];
+
+    for (int i = 0; i < 16; ++i)
+        pj_val_to_hex_digit(digest[i], &hash[2 * i]);
+
+    password_h = {hash, 32};
+}
+
+std::vector<std::map<std::string, std::string>>
+SipAccountConfig::getCredentials() const
+{
+    std::vector<std::map<std::string, std::string>> ret;
+    ret.reserve(credentials.size());
+    for (const auto& c : credentials) {
+        ret.emplace_back(c.toMap());
+    }
+    return ret;
+}
+
+void
+SipAccountConfig::setCredentials(const std::vector<std::map<std::string, std::string>>& creds)
+{
+    credentials.clear();
+    credentials.reserve(creds.size());
+    for (const auto& cred : creds)
+        credentials.emplace_back(cred);
+}
+
+}
diff --git a/src/sip/sipaccount_config.h b/src/sip/sipaccount_config.h
new file mode 100644
index 0000000000000000000000000000000000000000..b708e002cc724b8856ec013edc5089ddbe47eb0c
--- /dev/null
+++ b/src/sip/sipaccount_config.h
@@ -0,0 +1,130 @@
+/*
+ *  Copyright (C) 2004-2022 Savoir-faire Linux Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+#pragma once
+#include "sipaccountbase_config.h"
+
+namespace jami {
+constexpr static const char* ACCOUNT_TYPE_SIP = "SIP";
+
+struct SipAccountConfig : public SipAccountBaseConfig {
+    SipAccountConfig(const std::string& id = {}, const std::string& path = {}): SipAccountBaseConfig(ACCOUNT_TYPE_SIP, id, path) {}
+    void serialize(YAML::Emitter& out) const override;
+    void unserialize(const YAML::Node& node) override;
+    std::map<std::string, std::string> toMap() const override;
+    void fromMap(const std::map<std::string, std::string>&) override;
+
+    /**
+     * Local port to whih this account is bound
+     */
+    uint16_t localPort {sip_utils::DEFAULT_SIP_PORT};
+
+    /**
+     * Potential ip addresss on which this account is bound
+     */
+    std::string bindAddress {};
+
+    /**
+     * Published port, used only if defined by the user
+     */
+    uint16_t publishedPort {sip_utils::DEFAULT_SIP_PORT};
+
+    /**
+     * interface name on which this account is bound
+     */
+    std::string interface;
+
+    /**
+     * Determine if STUN public address resolution is required to register this account. In this
+     * case a STUN server hostname must be specified.
+     */
+    bool stunEnabled {false};
+
+    /**
+     * The STUN server hostname (optional), used to provide the public IP address in case the
+     * softphone stay behind a NAT.
+     */
+    std::string stunServer {};
+
+    /**
+     * Network settings
+     */
+    unsigned registrationExpire {3600};
+    bool registrationRefreshEnabled {true};
+
+    // If true, the contact addreass and header will be rewritten
+    // using the information received from the registrar.
+    bool allowIPAutoRewrite {true};
+
+    /**
+     * Input Outbound Proxy Server Address
+     */
+    std::string serviceRoute;
+
+    /**
+     * The TLS listener port
+     */
+    uint16_t tlsListenerPort {sip_utils::DEFAULT_SIP_TLS_PORT};
+    bool tlsEnable {false};
+    std::string tlsMethod;
+    std::string tlsCiphers;
+    std::string tlsServerName;
+    bool tlsVerifyServer {true};
+    bool tlsVerifyClient {true};
+    bool tlsRequireClientCertificate {true};
+    bool tlsDisableSecureDlgCheck {true};
+    int tlsNegotiationTimeout {2};
+
+    /**
+     * Determine if the softphone should fallback on non secured media channel if SRTP negotiation
+     * fails. Make sure other SIP endpoints share the same behavior since it could result in
+     * encrypted data to be played through the audio device.
+     */
+    bool srtpFallback {false};
+    /**
+     * Specifies the type of key exchange used for SRTP, if any.
+     * This only determine if the media channel is secured.
+     */
+    KeyExchangeProtocol srtpKeyExchange {KeyExchangeProtocol::NONE};
+
+    bool presenceEnabled {false};
+    bool publishSupported {false};
+    bool subscribeSupported {false};
+
+    /**
+     * Map of credential for this account
+     */
+    struct Credentials
+    {
+        std::string realm {};
+        std::string username {};
+        std::string password {};
+        std::string password_h {};
+        Credentials(const std::string& r, const std::string& u, const std::string& p)
+            : realm(r)
+            , username(u)
+            , password(p)
+        {}
+        Credentials(const std::map<std::string, std::string>& r);
+        std::map<std::string, std::string> toMap() const;
+        void computePasswordHash();
+    };
+    std::vector<Credentials> credentials;
+    std::vector<std::map<std::string, std::string>> getCredentials() const;
+    void setCredentials(const std::vector<std::map<std::string, std::string>>& creds);
+};
+
+}
diff --git a/src/sip/sipaccountbase.cpp b/src/sip/sipaccountbase.cpp
index 114d3a40b84d159ecf837a25c6dd4a4be7456e30..57e29c959de0f818f573cdf0fa5595636b719c59 100644
--- a/src/sip/sipaccountbase.cpp
+++ b/src/sip/sipaccountbase.cpp
@@ -128,203 +128,18 @@ SIPAccountBase::flush()
 {
     // Class base method
     Account::flush();
-
     fileutils::remove(fileutils::get_cache_dir() + DIR_SEPARATOR_STR + getAccountID()
                       + DIR_SEPARATOR_STR "messages");
 }
 
-template<typename T>
-static void
-validate(std::string& member, const std::string& param, const T& valid)
-{
-    const auto begin = std::begin(valid);
-    const auto end = std::end(valid);
-    if (find(begin, end, param) != end)
-        member = param;
-    else
-        JAMI_ERR("Invalid parameter \"%s\"", param.c_str());
-}
-
-static void
-updateRange(uint16_t min, uint16_t max, std::pair<uint16_t, uint16_t>& range)
-{
-    if (min > 0 and (max > min) and max <= SIPAccountBase::MAX_PORT - 2) {
-        range.first = min;
-        range.second = max;
-    }
-}
-
-static void
-unserializeRange(const YAML::Node& node,
-                 const char* minKey,
-                 const char* maxKey,
-                 std::pair<uint16_t, uint16_t>& range)
-{
-    int tmpMin = 0;
-    int tmpMax = 0;
-    yaml_utils::parseValue(node, minKey, tmpMin);
-    yaml_utils::parseValue(node, maxKey, tmpMax);
-    updateRange(tmpMin, tmpMax, range);
-}
-
-static void
-addRangeToDetails(std::map<std::string, std::string>& a,
-                  const char* minKey,
-                  const char* maxKey,
-                  const std::pair<uint16_t, uint16_t>& range)
-{
-    a.emplace(minKey, std::to_string(range.first));
-    a.emplace(maxKey, std::to_string(range.second));
-}
-
 void
-SIPAccountBase::serialize(YAML::Emitter& out) const
+SIPAccountBase::loadConfig()
 {
-    Account::serialize(out);
-
-    out << YAML::Key << Conf::AUDIO_PORT_MAX_KEY << YAML::Value << audioPortRange_.second;
-    out << YAML::Key << Conf::AUDIO_PORT_MIN_KEY << YAML::Value << audioPortRange_.first;
-    out << YAML::Key << Conf::DTMF_TYPE_KEY << YAML::Value << dtmfType_;
-    out << YAML::Key << Conf::INTERFACE_KEY << YAML::Value << interface_;
-    out << YAML::Key << Conf::PUBLISH_ADDR_KEY << YAML::Value << publishedIpAddress_;
-    out << YAML::Key << Conf::PUBLISH_PORT_KEY << YAML::Value << publishedPort_;
-    out << YAML::Key << Conf::SAME_AS_LOCAL_KEY << YAML::Value << publishedSameasLocal_;
-
-    out << YAML::Key << VIDEO_ENABLED_KEY << YAML::Value << videoEnabled_;
-    out << YAML::Key << Conf::VIDEO_PORT_MAX_KEY << YAML::Value << videoPortRange_.second;
-    out << YAML::Key << Conf::VIDEO_PORT_MIN_KEY << YAML::Value << videoPortRange_.first;
-
-    out << YAML::Key << Conf::STUN_ENABLED_KEY << YAML::Value << stunEnabled_;
-    out << YAML::Key << Conf::STUN_SERVER_KEY << YAML::Value << stunServer_;
-    out << YAML::Key << Conf::TURN_ENABLED_KEY << YAML::Value << turnEnabled_;
-    out << YAML::Key << Conf::TURN_SERVER_KEY << YAML::Value << turnServer_;
-    out << YAML::Key << Conf::TURN_SERVER_UNAME_KEY << YAML::Value << turnServerUserName_;
-    out << YAML::Key << Conf::TURN_SERVER_PWD_KEY << YAML::Value << turnServerPwd_;
-    out << YAML::Key << Conf::TURN_SERVER_REALM_KEY << YAML::Value << turnServerRealm_;
-}
-
-void
-SIPAccountBase::serializeTls(YAML::Emitter& out) const
-{
-    out << YAML::Key << Conf::CALIST_KEY << YAML::Value << tlsCaListFile_;
-    out << YAML::Key << Conf::CERTIFICATE_KEY << YAML::Value << tlsCertificateFile_;
-    out << YAML::Key << Conf::TLS_PASSWORD_KEY << YAML::Value << tlsPassword_;
-    out << YAML::Key << Conf::PRIVATE_KEY_KEY << YAML::Value << tlsPrivateKeyFile_;
-}
-
-void
-SIPAccountBase::unserialize(const YAML::Node& node)
-{
-    using yaml_utils::parseValue;
-    using yaml_utils::parseValueOptional;
-    using yaml_utils::parseVectorMap;
-
-    Account::unserialize(node);
-
-    parseValue(node, VIDEO_ENABLED_KEY, videoEnabled_);
-
-    parseValue(node, Conf::INTERFACE_KEY, interface_);
-    parseValue(node, Conf::SAME_AS_LOCAL_KEY, publishedSameasLocal_);
-    parseValue(node, Conf::PUBLISH_ADDR_KEY, publishedIpAddress_);
-    IpAddr publishedIp {publishedIpAddress_};
-    if (publishedIp and not publishedSameasLocal_)
+    Account::loadConfig();
+    const auto& conf = config();
+    IpAddr publishedIp {conf.publishedIp};
+    if (not conf.publishedSameasLocal and publishedIp)
         setPublishedAddress(publishedIp);
-
-    int port = sip_utils::DEFAULT_SIP_PORT;
-    parseValue(node, Conf::PUBLISH_PORT_KEY, port);
-    publishedPort_ = port;
-
-    parseValue(node, Conf::DTMF_TYPE_KEY, dtmfType_);
-
-    unserializeRange(node, Conf::AUDIO_PORT_MIN_KEY, Conf::AUDIO_PORT_MAX_KEY, audioPortRange_);
-    unserializeRange(node, Conf::VIDEO_PORT_MIN_KEY, Conf::VIDEO_PORT_MAX_KEY, videoPortRange_);
-
-    // ICE - STUN/TURN
-    if (not isIP2IP()) {
-        parseValue(node, Conf::STUN_ENABLED_KEY, stunEnabled_);
-        parseValue(node, Conf::STUN_SERVER_KEY, stunServer_);
-        parseValue(node, Conf::TURN_ENABLED_KEY, turnEnabled_);
-        parseValue(node, Conf::TURN_SERVER_KEY, turnServer_);
-        parseValue(node, Conf::TURN_SERVER_UNAME_KEY, turnServerUserName_);
-        parseValue(node, Conf::TURN_SERVER_PWD_KEY, turnServerPwd_);
-        parseValue(node, Conf::TURN_SERVER_REALM_KEY, turnServerRealm_);
-    }
-}
-
-void
-SIPAccountBase::setAccountDetails(const std::map<std::string, std::string>& details)
-{
-    Account::setAccountDetails(details);
-
-    parseBool(details, Conf::CONFIG_VIDEO_ENABLED, videoEnabled_);
-
-    // general sip settings
-    parseString(details, Conf::CONFIG_LOCAL_INTERFACE, interface_);
-    parseBool(details, Conf::CONFIG_PUBLISHED_SAMEAS_LOCAL, publishedSameasLocal_);
-    parseString(details, Conf::CONFIG_PUBLISHED_ADDRESS, publishedIpAddress_);
-    parseInt(details, Conf::CONFIG_PUBLISHED_PORT, publishedPort_);
-    IpAddr publishedIp {publishedIpAddress_};
-    if (publishedIp and not publishedSameasLocal_)
-        setPublishedAddress(publishedIp);
-
-    parseString(details, Conf::CONFIG_ACCOUNT_DTMF_TYPE, dtmfType_);
-
-    int tmpMin = -1;
-    parseInt(details, Conf::CONFIG_ACCOUNT_AUDIO_PORT_MIN, tmpMin);
-    int tmpMax = -1;
-    parseInt(details, Conf::CONFIG_ACCOUNT_AUDIO_PORT_MAX, tmpMax);
-    updateRange(tmpMin, tmpMax, audioPortRange_);
-#ifdef ENABLE_VIDEO
-    tmpMin = -1;
-    parseInt(details, Conf::CONFIG_ACCOUNT_VIDEO_PORT_MIN, tmpMin);
-    tmpMax = -1;
-    parseInt(details, Conf::CONFIG_ACCOUNT_VIDEO_PORT_MAX, tmpMax);
-    updateRange(tmpMin, tmpMax, videoPortRange_);
-#endif
-
-    // ICE - STUN
-    parseBool(details, Conf::CONFIG_STUN_ENABLE, stunEnabled_);
-    parseString(details, Conf::CONFIG_STUN_SERVER, stunServer_);
-
-    // ICE - TURN
-    parseBool(details, Conf::CONFIG_TURN_ENABLE, turnEnabled_);
-    parseString(details, Conf::CONFIG_TURN_SERVER, turnServer_);
-    parseString(details, Conf::CONFIG_TURN_SERVER_UNAME, turnServerUserName_);
-    parseString(details, Conf::CONFIG_TURN_SERVER_PWD, turnServerPwd_);
-    parseString(details, Conf::CONFIG_TURN_SERVER_REALM, turnServerRealm_);
-}
-
-std::map<std::string, std::string>
-SIPAccountBase::getAccountDetails() const
-{
-    auto a = Account::getAccountDetails();
-    a.emplace(Conf::CONFIG_VIDEO_ENABLED, videoEnabled_ ? TRUE_STR : FALSE_STR);
-
-    addRangeToDetails(a,
-                      Conf::CONFIG_ACCOUNT_AUDIO_PORT_MIN,
-                      Conf::CONFIG_ACCOUNT_AUDIO_PORT_MAX,
-                      audioPortRange_);
-#ifdef ENABLE_VIDEO
-    addRangeToDetails(a,
-                      Conf::CONFIG_ACCOUNT_VIDEO_PORT_MIN,
-                      Conf::CONFIG_ACCOUNT_VIDEO_PORT_MAX,
-                      videoPortRange_);
-#endif
-
-    a.emplace(Conf::CONFIG_ACCOUNT_DTMF_TYPE, dtmfType_);
-    a.emplace(Conf::CONFIG_LOCAL_INTERFACE, interface_);
-    a.emplace(Conf::CONFIG_PUBLISHED_PORT, std::to_string(publishedPort_));
-    a.emplace(Conf::CONFIG_PUBLISHED_SAMEAS_LOCAL, publishedSameasLocal_ ? TRUE_STR : FALSE_STR);
-    a.emplace(Conf::CONFIG_PUBLISHED_ADDRESS, publishedIpAddress_);
-    a.emplace(Conf::CONFIG_STUN_ENABLE, stunEnabled_ ? TRUE_STR : FALSE_STR);
-    a.emplace(Conf::CONFIG_STUN_SERVER, stunServer_);
-    a.emplace(Conf::CONFIG_TURN_ENABLE, turnEnabled_ ? TRUE_STR : FALSE_STR);
-    a.emplace(Conf::CONFIG_TURN_SERVER, turnServer_);
-    a.emplace(Conf::CONFIG_TURN_SERVER_UNAME, turnServerUserName_);
-    a.emplace(Conf::CONFIG_TURN_SERVER_PWD, turnServerPwd_);
-    a.emplace(Conf::CONFIG_TURN_SERVER_REALM, turnServerRealm_);
-
-    return a;
 }
 
 std::map<std::string, std::string>
@@ -404,14 +219,14 @@ SIPAccountBase::releasePort(uint16_t port) noexcept
 uint16_t
 SIPAccountBase::generateAudioPort() const
 {
-    return acquireRandomEvenPort(audioPortRange_);
+    return acquireRandomEvenPort(config().audioPortRange);
 }
 
 #ifdef ENABLE_VIDEO
 uint16_t
 SIPAccountBase::generateVideoPort() const
 {
-    return acquireRandomEvenPort(videoPortRange_);
+    return acquireRandomEvenPort(config().videoPortRange);
 }
 #endif
 
@@ -421,18 +236,18 @@ SIPAccountBase::getIceOptions() const noexcept
     IceTransportOptions opts;
     opts.upnpEnable = getUPnPActive();
 
-    if (stunEnabled_)
-        opts.stunServers.emplace_back(StunServerInfo().setUri(stunServer_));
-    if (turnEnabled_) {
+    //if (config().stunEnabled)
+    //    opts.stunServers.emplace_back(StunServerInfo().setUri(stunServer_));
+    if (config().turnEnabled) {
         auto cached = false;
         std::lock_guard<std::mutex> lk(cachedTurnMutex_);
         cached = cacheTurnV4_ || cacheTurnV6_;
         if (cacheTurnV4_ && *cacheTurnV4_) {
             opts.turnServers.emplace_back(TurnServerInfo()
                                               .setUri(cacheTurnV4_->toString(true))
-                                              .setUsername(turnServerUserName_)
-                                              .setPassword(turnServerPwd_)
-                                              .setRealm(turnServerRealm_));
+                                              .setUsername(config().turnServerUserName)
+                                              .setPassword(config().turnServerPwd)
+                                              .setRealm(config().turnServerRealm));
         }
         // NOTE: first test with ipv6 turn was not concluant and resulted in multiple
         // co issues. So this needs some debug. for now just disable
@@ -446,10 +261,10 @@ SIPAccountBase::getIceOptions() const noexcept
         // Nothing cached, so do the resolution
         if (!cached) {
             opts.turnServers.emplace_back(TurnServerInfo()
-                                              .setUri(turnServer_)
-                                              .setUsername(turnServerUserName_)
-                                              .setPassword(turnServerPwd_)
-                                              .setRealm(turnServerRealm_));
+                                              .setUri(config().turnServer)
+                                              .setUsername(config().turnServerUserName)
+                                              .setPassword(config().turnServerPwd)
+                                              .setRealm(config().turnServerRealm));
         }
     }
     return opts;
diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h
index 011360606be58cc02337519d33d3f3367984b47d..a660d8266400e11f30886f2a90f284bb81fc39a4 100644
--- a/src/sip/sipaccountbase.h
+++ b/src/sip/sipaccountbase.h
@@ -30,6 +30,7 @@
 #include "connectivity/ip_utils.h"
 #include "noncopyable.h"
 #include "im/message_engine.h"
+#include "sipaccountbase_config.h"
 
 #include <array>
 #include <deque>
@@ -58,54 +59,6 @@ namespace jami {
 class SipTransport;
 class Task;
 
-namespace Conf {
-// SIP specific configuration keys
-const char* const BIND_ADDRESS_KEY = "bindAddress";
-const char* const INTERFACE_KEY = "interface";
-const char* const PORT_KEY = "port";
-const char* const PUBLISH_ADDR_KEY = "publishAddr";
-const char* const PUBLISH_PORT_KEY = "publishPort";
-const char* const SAME_AS_LOCAL_KEY = "sameasLocal";
-const char* const DTMF_TYPE_KEY = "dtmfType";
-const char* const SERVICE_ROUTE_KEY = "serviceRoute";
-const char* const ALLOW_IP_AUTO_REWRITE = "allowIPAutoRewrite";
-const char* const PRESENCE_ENABLED_KEY = "presenceEnabled";
-const char* const PRESENCE_PUBLISH_SUPPORTED_KEY = "presencePublishSupported";
-const char* const PRESENCE_SUBSCRIBE_SUPPORTED_KEY = "presenceSubscribeSupported";
-const char* const PRESENCE_STATUS_KEY = "presenceStatus";
-const char* const PRESENCE_NOTE_KEY = "presenceNote";
-
-// TODO: write an object to store tls params which implement serializable
-const char* const TLS_KEY = "tls";
-const char* const TLS_PORT_KEY = "tlsPort";
-const char* const CERTIFICATE_KEY = "certificate";
-const char* const CALIST_KEY = "calist";
-const char* const CIPHERS_KEY = "ciphers";
-const char* const TLS_ENABLE_KEY = "enable";
-const char* const METHOD_KEY = "method";
-const char* const TIMEOUT_KEY = "timeout";
-const char* const TLS_PASSWORD_KEY = "password";
-const char* const PRIVATE_KEY_KEY = "privateKey";
-const char* const REQUIRE_CERTIF_KEY = "requireCertif";
-const char* const DISABLE_SECURE_DLG_CHECK = "disableSecureDlgCheck";
-const char* const SERVER_KEY = "server";
-const char* const VERIFY_CLIENT_KEY = "verifyClient";
-const char* const VERIFY_SERVER_KEY = "verifyServer";
-
-const char* const STUN_ENABLED_KEY = "stunEnabled";
-const char* const STUN_SERVER_KEY = "stunServer";
-const char* const TURN_ENABLED_KEY = "turnEnabled";
-const char* const TURN_SERVER_KEY = "turnServer";
-const char* const TURN_SERVER_UNAME_KEY = "turnServerUserName";
-const char* const TURN_SERVER_PWD_KEY = "turnServerPassword";
-const char* const TURN_SERVER_REALM_KEY = "turnServerRealm";
-const char* const CRED_KEY = "credential";
-const char* const AUDIO_PORT_MIN_KEY = "audioPortMin";
-const char* const AUDIO_PORT_MAX_KEY = "audioPortMax";
-const char* const VIDEO_PORT_MIN_KEY = "videoPortMin";
-const char* const VIDEO_PORT_MAX_KEY = "videoPortMax";
-} // namespace Conf
-
 typedef std::vector<pj_ssl_cipher> CipherArray;
 
 class SIPVoIPLink;
@@ -121,8 +74,6 @@ enum class MatchRank { NONE, PARTIAL, FULL };
 class SIPAccountBase : public Account
 {
 public:
-    constexpr static const char* const OVERRTP_STR = "overrtp";
-    constexpr static const char* const SIPINFO_STR = "sipinfo";
     constexpr static unsigned MAX_PORT {65536};
     constexpr static unsigned HALF_MAX_PORT {MAX_PORT / 2};
 
@@ -134,6 +85,12 @@ public:
 
     virtual ~SIPAccountBase() noexcept;
 
+    const SipAccountBaseConfig& config() const {
+        return *static_cast<const SipAccountBaseConfig*>(&Account::config());
+    }
+
+    void loadConfig() override;
+
     /**
      * Create incoming SIPCall.
      * @param[in] from The origin of the call
@@ -148,11 +105,9 @@ public:
 
     virtual bool isStunEnabled() const { return false; }
 
-    virtual pj_str_t getStunServerName() const { return pj_str_t {nullptr, 0}; };
-
     virtual pj_uint16_t getStunPort() const { return 0; };
 
-    virtual std::string getDtmfType() const { return dtmfType_; }
+    virtual std::string getDtmfType() const { return config().dtmfType; }
 
     /**
      * Determine if TLS is enabled for this account. TLS provides a secured channel for
@@ -176,7 +131,7 @@ public:
     /**
      * Get the local interface name on which this account is bound.
      */
-    const std::string& getLocalInterface() const { return interface_; }
+    const std::string& getLocalInterface() const { return config().interface; }
 
     /**
      * Get the public IP address set by the user for this account.
@@ -184,32 +139,18 @@ public:
      * will be used.
      * @return std::string The public IPv4 or IPv6 address formatted in standard notation.
      */
-    std::string getPublishedAddress() const { return publishedIpAddress_; }
+    std::string getPublishedAddress() const { return config().publishedIp; }
 
     IpAddr getPublishedIpAddress(uint16_t family = PF_UNSPEC) const;
 
     void setPublishedAddress(const IpAddr& ip_addr);
 
-    /**
-     * Get the published port, which is the port to be advertised as the port
-     * for the chosen SIP transport.
-     * @return pj_uint16 The port used for that account
-     */
-    pj_uint16_t getPublishedPort() const { return (pj_uint16_t) publishedPort_; }
-
-    /**
-     * Set the published port, which is the port to be advertised as the port
-     * for the chosen SIP transport.
-     * @pram port The port used by this account.
-     */
-    void setPublishedPort(pj_uint16_t port) { publishedPort_ = port; }
-
     /**
      * Get a flag which determine the usage in sip headers of either the local
      * IP address and port (_localAddress and localPort_) or to an address set
      * manually (_publishedAddress and publishedPort_).
      */
-    bool getPublishedSameasLocal() const { return publishedSameasLocal_; }
+    bool getPublishedSameasLocal() const { return config().publishedSameasLocal; }
 
     virtual bool isSrtpEnabled() const = 0;
 
@@ -229,21 +170,6 @@ public:
 #endif
     static void releasePort(uint16_t port) noexcept;
 
-    /**
-     * @return pj_str_t , filled from the configuration
-     * file, that can be used directly by PJSIP to initialize
-     * an alternate UDP transport.
-     */
-    std::string getStunServer() const
-    {
-        return stunServer_;
-    }
-
-    void setStunServer(const std::string& srv)
-    {
-        stunServer_ = srv;
-    }
-
     IceTransportOptions getIceOptions() const noexcept;
 
     virtual void sendMessage(const std::string& to,
@@ -306,14 +232,6 @@ public: // overloaded methods
     }
 
 protected:
-    virtual void serialize(YAML::Emitter& out) const override;
-    virtual void serializeTls(YAML::Emitter& out) const;
-    virtual void unserialize(const YAML::Node& node) override;
-
-    virtual void setAccountDetails(const std::map<std::string, std::string>& details) override;
-
-    virtual std::map<std::string, std::string> getAccountDetails() const override;
-
     /**
      * Retrieve volatile details such as recent registration errors
      * @return std::map< std::string, std::string > The account volatile details
@@ -331,17 +249,6 @@ protected:
      */
     SIPVoIPLink& link_;
 
-    /**
-     * interface name on which this account is bound
-     */
-    std::string interface_ {"default"};
-
-    /**
-     * Flag which determine if localIpAddress_ or publishedIpAddress_ is used in
-     * sip headers
-     */
-    bool publishedSameasLocal_ {true};
-
     /**
      * Published IPv4/IPv6 addresses, used only if defined by the user in account
      * configuration
@@ -349,64 +256,9 @@ protected:
      */
     IpAddr publishedIp_[2] {};
 
-    // This will be stored in the configuration
-    std::string publishedIpAddress_ {};
-
-    /**
-     * Published port, used only if defined by the user
-     */
-    pj_uint16_t publishedPort_ {sip_utils::DEFAULT_SIP_PORT};
-
-    /**
-     * Determine if STUN public address resolution is required to register this account. In this
-     * case a STUN server hostname must be specified.
-     */
-    bool stunEnabled_ {false};
-
-    /**
-     * The STUN server hostname (optional), used to provide the public IP address in case the
-     * softphone stay behind a NAT.
-     */
-    std::string stunServer_ {};
-
-    /**
-     * Determine if TURN public address resolution is required to register this account. In this
-     * case a TURN server hostname must be specified.
-     */
-    bool turnEnabled_ {false};
-
-    /**
-     * The TURN server hostname (optional), used to provide the public IP address in case the
-     * softphone stay behind a NAT.
-     */
-    std::string turnServer_;
-    std::string turnServerUserName_;
-    std::string turnServerPwd_;
-    std::string turnServerRealm_;
-
-    std::string tlsCaListFile_;
-    std::string tlsCertificateFile_;
-    std::string tlsPrivateKeyFile_;
-    std::string tlsPassword_;
-
-    /**
-     * DTMF type used for this account SIPINFO or RTP
-     */
-    std::string dtmfType_ {OVERRTP_STR};
-
     pj_status_t transportStatus_ {PJSIP_SC_TRYING};
     std::string transportError_ {};
 
-    /*
-     * Port range for audio RTP ports
-     */
-    std::pair<uint16_t, uint16_t> audioPortRange_ {16384, 32766};
-
-    /**
-     * Port range for video RTP ports
-     */
-    std::pair<uint16_t, uint16_t> videoPortRange_ {49152, (MAX_PORT) -2};
-
     static std::array<bool, HALF_MAX_PORT>& getPortsReservation() noexcept;
     static uint16_t acquirePort(uint16_t port);
     uint16_t getRandomEvenPort(const std::pair<uint16_t, uint16_t>& range) const;
diff --git a/src/sip/sipaccountbase_config.cpp b/src/sip/sipaccountbase_config.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a8a668b0ba297450b3a1442764899e7b65ec2c38
--- /dev/null
+++ b/src/sip/sipaccountbase_config.cpp
@@ -0,0 +1,187 @@
+/*
+ *  Copyright (C) 2004-2022 Savoir-faire Linux Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+#include "sipaccountbase_config.h"
+#include "account_const.h"
+#include "account_schema.h"
+#include "config/yamlparser.h"
+
+namespace jami {
+
+namespace Conf {
+// SIP specific configuration keys
+const char* const BIND_ADDRESS_KEY = "bindAddress";
+const char* const INTERFACE_KEY = "interface";
+const char* const PORT_KEY = "port";
+const char* const PUBLISH_ADDR_KEY = "publishAddr";
+const char* const PUBLISH_PORT_KEY = "publishPort";
+const char* const SAME_AS_LOCAL_KEY = "sameasLocal";
+const char* const DTMF_TYPE_KEY = "dtmfType";
+const char* const SERVICE_ROUTE_KEY = "serviceRoute";
+const char* const ALLOW_IP_AUTO_REWRITE = "allowIPAutoRewrite";
+const char* const PRESENCE_ENABLED_KEY = "presenceEnabled";
+const char* const PRESENCE_PUBLISH_SUPPORTED_KEY = "presencePublishSupported";
+const char* const PRESENCE_SUBSCRIBE_SUPPORTED_KEY = "presenceSubscribeSupported";
+const char* const PRESENCE_STATUS_KEY = "presenceStatus";
+const char* const PRESENCE_NOTE_KEY = "presenceNote";
+const char* const STUN_ENABLED_KEY = "stunEnabled";
+const char* const STUN_SERVER_KEY = "stunServer";
+const char* const TURN_ENABLED_KEY = "turnEnabled";
+const char* const TURN_SERVER_KEY = "turnServer";
+const char* const TURN_SERVER_UNAME_KEY = "turnServerUserName";
+const char* const TURN_SERVER_PWD_KEY = "turnServerPassword";
+const char* const TURN_SERVER_REALM_KEY = "turnServerRealm";
+const char* const CRED_KEY = "credential";
+const char* const AUDIO_PORT_MIN_KEY = "audioPortMin";
+const char* const AUDIO_PORT_MAX_KEY = "audioPortMax";
+const char* const VIDEO_PORT_MIN_KEY = "videoPortMin";
+const char* const VIDEO_PORT_MAX_KEY = "videoPortMax";
+} // namespace Conf
+
+using yaml_utils::parseValueOptional;
+
+static void
+unserializeRange(const YAML::Node& node,
+                 const char* minKey,
+                 const char* maxKey,
+                 std::pair<uint16_t, uint16_t>& range)
+{
+    int tmpMin = yaml_utils::parseValueOptional(node, minKey, tmpMin);
+    int tmpMax = yaml_utils::parseValueOptional(node, maxKey, tmpMax);
+    updateRange(tmpMin, tmpMax, range);
+}
+
+static void
+addRangeToDetails(std::map<std::string, std::string>& a,
+                  const char* minKey,
+                  const char* maxKey,
+                  const std::pair<uint16_t, uint16_t>& range)
+{
+    a.emplace(minKey, std::to_string(range.first));
+    a.emplace(maxKey, std::to_string(range.second));
+}
+
+void
+SipAccountBaseConfig::serialize(YAML::Emitter& out) const
+{
+    AccountConfig::serialize(out);
+    out << YAML::Key << Conf::AUDIO_PORT_MAX_KEY << YAML::Value << audioPortRange.second;
+    out << YAML::Key << Conf::AUDIO_PORT_MIN_KEY << YAML::Value << audioPortRange.first;
+    out << YAML::Key << Conf::DTMF_TYPE_KEY << YAML::Value << dtmfType;
+    out << YAML::Key << Conf::INTERFACE_KEY << YAML::Value << interface;
+    out << YAML::Key << Conf::PUBLISH_ADDR_KEY << YAML::Value << publishedIp;
+    //out << YAML::Key << Conf::PUBLISH_PORT_KEY << YAML::Value << publishedPort;
+    out << YAML::Key << Conf::SAME_AS_LOCAL_KEY << YAML::Value << publishedSameasLocal;
+
+    out << YAML::Key << Conf::VIDEO_PORT_MAX_KEY << YAML::Value << videoPortRange.second;
+    out << YAML::Key << Conf::VIDEO_PORT_MIN_KEY << YAML::Value << videoPortRange.first;
+
+    out << YAML::Key << Conf::TURN_ENABLED_KEY << YAML::Value << turnEnabled;
+    out << YAML::Key << Conf::TURN_SERVER_KEY << YAML::Value << turnServer;
+    out << YAML::Key << Conf::TURN_SERVER_UNAME_KEY << YAML::Value << turnServerUserName;
+    out << YAML::Key << Conf::TURN_SERVER_PWD_KEY << YAML::Value << turnServerPwd;
+    out << YAML::Key << Conf::TURN_SERVER_REALM_KEY << YAML::Value << turnServerRealm;
+
+    /*out << YAML::Key << Conf::CALIST_KEY << YAML::Value << tlsCaListFile;
+    out << YAML::Key << Conf::CERTIFICATE_KEY << YAML::Value << tlsCertificateFile;
+    out << YAML::Key << Conf::TLS_PASSWORD_KEY << YAML::Value << tlsPassword;
+    out << YAML::Key << Conf::PRIVATE_KEY_KEY << YAML::Value << tlsPrivateKeyFile;*/
+}
+
+
+void
+SipAccountBaseConfig::unserialize(const YAML::Node& node)
+{
+    AccountConfig::unserialize(node);
+    parseValueOptional(node, Conf::INTERFACE_KEY, interface);
+    parseValueOptional(node, Conf::SAME_AS_LOCAL_KEY, publishedSameasLocal);
+    parseValueOptional(node, Conf::PUBLISH_ADDR_KEY, publishedIp);
+    parseValueOptional(node, Conf::DTMF_TYPE_KEY, dtmfType);
+    unserializeRange(node, Conf::AUDIO_PORT_MIN_KEY, Conf::AUDIO_PORT_MAX_KEY, audioPortRange);
+    unserializeRange(node, Conf::VIDEO_PORT_MIN_KEY, Conf::VIDEO_PORT_MAX_KEY, videoPortRange);
+
+    // ICE - STUN/TURN
+    //parseValueOptional(node, Conf::STUN_ENABLED_KEY, stunEnabled);
+    //parseValueOptional(node, Conf::STUN_SERVER_KEY, stunServer);
+    parseValueOptional(node, Conf::TURN_ENABLED_KEY, turnEnabled);
+    parseValueOptional(node, Conf::TURN_SERVER_KEY, turnServer);
+    parseValueOptional(node, Conf::TURN_SERVER_UNAME_KEY, turnServerUserName);
+    parseValueOptional(node, Conf::TURN_SERVER_PWD_KEY, turnServerPwd);
+    parseValueOptional(node, Conf::TURN_SERVER_REALM_KEY, turnServerRealm);
+}
+
+std::map<std::string, std::string>
+SipAccountBaseConfig::toMap() const
+{
+    auto a = AccountConfig::toMap();
+
+    addRangeToDetails(a,
+                      Conf::CONFIG_ACCOUNT_AUDIO_PORT_MIN,
+                      Conf::CONFIG_ACCOUNT_AUDIO_PORT_MAX,
+                      audioPortRange);
+    addRangeToDetails(a,
+                      Conf::CONFIG_ACCOUNT_VIDEO_PORT_MIN,
+                      Conf::CONFIG_ACCOUNT_VIDEO_PORT_MAX,
+                      videoPortRange);
+
+    a.emplace(Conf::CONFIG_ACCOUNT_DTMF_TYPE, dtmfType);
+    a.emplace(Conf::CONFIG_LOCAL_INTERFACE, interface);
+    a.emplace(Conf::CONFIG_PUBLISHED_SAMEAS_LOCAL, publishedSameasLocal ? TRUE_STR : FALSE_STR);
+    a.emplace(Conf::CONFIG_PUBLISHED_ADDRESS, publishedIp);
+
+    a.emplace(Conf::CONFIG_TURN_ENABLE, turnEnabled ? TRUE_STR : FALSE_STR);
+    a.emplace(Conf::CONFIG_TURN_SERVER, turnServer);
+    a.emplace(Conf::CONFIG_TURN_SERVER_UNAME, turnServerUserName);
+    a.emplace(Conf::CONFIG_TURN_SERVER_PWD, turnServerPwd);
+    a.emplace(Conf::CONFIG_TURN_SERVER_REALM, turnServerRealm);
+    return a;
+}
+
+void
+SipAccountBaseConfig::fromMap(const std::map<std::string, std::string>& details)
+{
+    AccountConfig::fromMap(details);
+
+    // general sip settings
+    parseString(details, Conf::CONFIG_LOCAL_INTERFACE, interface);
+    parseBool(details, Conf::CONFIG_PUBLISHED_SAMEAS_LOCAL, publishedSameasLocal);
+    parseString(details, Conf::CONFIG_PUBLISHED_ADDRESS, publishedIp);
+    parseString(details, Conf::CONFIG_ACCOUNT_DTMF_TYPE, dtmfType);
+
+    int tmpMin = -1;
+    parseInt(details, Conf::CONFIG_ACCOUNT_AUDIO_PORT_MIN, tmpMin);
+    int tmpMax = -1;
+    parseInt(details, Conf::CONFIG_ACCOUNT_AUDIO_PORT_MAX, tmpMax);
+    updateRange(tmpMin, tmpMax, audioPortRange);
+    tmpMin = -1;
+    parseInt(details, Conf::CONFIG_ACCOUNT_VIDEO_PORT_MIN, tmpMin);
+    tmpMax = -1;
+    parseInt(details, Conf::CONFIG_ACCOUNT_VIDEO_PORT_MAX, tmpMax);
+    updateRange(tmpMin, tmpMax, videoPortRange);
+
+    // ICE - STUN
+    //parseBool(details, Conf::CONFIG_STUN_ENABLE, stunEnabled);
+    //parseString(details, Conf::CONFIG_STUN_SERVER, stunServer);
+
+    // ICE - TURN
+    parseBool(details, Conf::CONFIG_TURN_ENABLE, turnEnabled);
+    parseString(details, Conf::CONFIG_TURN_SERVER, turnServer);
+    parseString(details, Conf::CONFIG_TURN_SERVER_UNAME, turnServerUserName);
+    parseString(details, Conf::CONFIG_TURN_SERVER_PWD, turnServerPwd);
+    parseString(details, Conf::CONFIG_TURN_SERVER_REALM, turnServerRealm);
+}
+
+}
diff --git a/src/sip/sipaccountbase_config.h b/src/sip/sipaccountbase_config.h
new file mode 100644
index 0000000000000000000000000000000000000000..2a60efa4e1c1cc6a8d8a2609c2ae96333b484a7e
--- /dev/null
+++ b/src/sip/sipaccountbase_config.h
@@ -0,0 +1,89 @@
+/*
+ *  Copyright (C) 2004-2022 Savoir-faire Linux Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+#pragma once
+#include "account_config.h"
+
+namespace jami {
+constexpr static const char* const OVERRTP_STR = "overrtp";
+constexpr static const char* const SIPINFO_STR = "sipinfo";
+constexpr static unsigned MAX_PORT {65536};
+constexpr static unsigned HALF_MAX_PORT {MAX_PORT / 2};
+
+struct SipAccountBaseConfig: public AccountConfig {
+    SipAccountBaseConfig(const std::string& type, const std::string& id, const std::string& path): AccountConfig(type, id, path) {}
+
+    void serialize(YAML::Emitter& out) const override;
+    void unserialize(const YAML::Node& node) override;
+
+    std::map<std::string, std::string> toMap() const override;
+    void fromMap(const std::map<std::string, std::string>&) override;
+
+    /**
+     * interface name on which this account is bound
+     */
+    std::string interface {"default"};
+
+    /**
+     * Flag which determine if localIpAddress_ or publishedIpAddress_ is used in
+     * sip headers
+     */
+    bool publishedSameasLocal {true};
+
+    std::string publishedIp;
+
+    /**
+     * Determine if TURN public address resolution is required to register this account. In this
+     * case a TURN server hostname must be specified.
+     */
+    bool turnEnabled {false};
+
+    /**
+     * The TURN server hostname (optional), used to provide the public IP address in case the
+     * softphone stay behind a NAT.
+     */
+    std::string turnServer;
+    std::string turnServerUserName;
+    std::string turnServerPwd;
+    std::string turnServerRealm;
+
+    std::string tlsCaListFile;
+    std::string tlsCertificateFile;
+    std::string tlsPrivateKeyFile;
+    std::string tlsPassword;
+
+    std::string dtmfType {OVERRTP_STR};
+    /*
+     * Port range for audio RTP ports
+     */
+    std::pair<uint16_t, uint16_t> audioPortRange {16384, 32766};
+
+    /**
+     * Port range for video RTP ports
+     */
+    std::pair<uint16_t, uint16_t> videoPortRange {49152, (65536) -2};
+};
+
+inline void
+updateRange(uint16_t min, uint16_t max, std::pair<uint16_t, uint16_t>& range)
+{
+    if (min > 0 and (max > min) and max <= MAX_PORT - 2) {
+        range.first = min;
+        range.second = max;
+    }
+}
+
+}
diff --git a/test/unitTest/ice/ice_media_cand_exchange.cpp b/test/unitTest/ice/ice_media_cand_exchange.cpp
index 47d24078ef55bc80427e026d762df23d6c665459..fe94ccc515721502caf35e7891490826e77ceb31 100644
--- a/test/unitTest/ice/ice_media_cand_exchange.cpp
+++ b/test/unitTest/ice/ice_media_cand_exchange.cpp
@@ -355,9 +355,6 @@ IceMediaCandExchangeTest::setupSipAccount(CallData& user)
     details = account->getAccountDetails();
     user.userName_ = details[ConfProperties::USERNAME];
     user.alias_ = details[ConfProperties::ALIAS];
-
-    account->enableIceForMedia(true);
-
     user.dest_ = ip_utils::getLocalAddr(AF_INET);
     user.dest_.setPort(user.listeningPort_);
 }
diff --git a/test/unitTest/ice/ice_sdp_parser.cpp b/test/unitTest/ice/ice_sdp_parser.cpp
index bc9be629282b1332aae436d28350d95a2bb60680..6ab8e9318432df3cb02cb789fb14ec900beddb74 100644
--- a/test/unitTest/ice/ice_sdp_parser.cpp
+++ b/test/unitTest/ice/ice_sdp_parser.cpp
@@ -439,7 +439,6 @@ IceSdpParsingTest::configureTest(CallData& aliceData, CallData& bobData)
         aliceData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
         aliceData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
         account->setLocalPort(aliceData.listeningPort_);
-        account->enableIceForMedia(true);
         account->enableIceCompIdRfc5245Compliance(aliceData.compliancyEnabled_);
     }
 
diff --git a/test/unitTest/media_negotiation/auto_answer.cpp b/test/unitTest/media_negotiation/auto_answer.cpp
index d8a15a0d366fe7a382b16b434f0a27c3b4416089..21f1d67655ed055bf33d19ed7dcff75a5401ef2a 100644
--- a/test/unitTest/media_negotiation/auto_answer.cpp
+++ b/test/unitTest/media_negotiation/auto_answer.cpp
@@ -599,7 +599,6 @@ AutoAnswerMediaNegoTest::configureScenario()
         auto const& account = Manager::instance().getAccount<Account>(aliceData_.accountId_);
         aliceData_.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
         aliceData_.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
-        account->enableIceForMedia(true);
         if (isSipAccount_) {
             auto sipAccount = std::dynamic_pointer_cast<SIPAccount>(account);
             CPPUNIT_ASSERT(sipAccount);
@@ -613,7 +612,6 @@ AutoAnswerMediaNegoTest::configureScenario()
         auto const& account = Manager::instance().getAccount<Account>(bobData_.accountId_);
         bobData_.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
         bobData_.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
-        account->enableIceForMedia(true);
         CPPUNIT_ASSERT(account->isAutoAnswerEnabled());
 
         if (isSipAccount_) {
diff --git a/test/unitTest/media_negotiation/media_negotiation.cpp b/test/unitTest/media_negotiation/media_negotiation.cpp
index 52ff0ceac318f21c986a402030ee96312e155b99..52c8c6128fcdf276907ee7e019c6084bbe3072af 100644
--- a/test/unitTest/media_negotiation/media_negotiation.cpp
+++ b/test/unitTest/media_negotiation/media_negotiation.cpp
@@ -676,7 +676,6 @@ MediaNegotiationTest::configureScenario()
             callDataMap_["ALICE"].accountId_);
         callDataMap_["ALICE"].userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
         callDataMap_["ALICE"].alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
-        account->enableIceForMedia(true);
         if (isSipAccount_) {
             auto sipAccount = std::dynamic_pointer_cast<SIPAccount>(account);
             CPPUNIT_ASSERT(sipAccount);
@@ -691,7 +690,6 @@ MediaNegotiationTest::configureScenario()
             callDataMap_["BOB"].accountId_);
         callDataMap_["BOB"].userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
         callDataMap_["BOB"].alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
-        account->enableIceForMedia(true);
 
         if (isSipAccount_) {
             auto sipAccount = std::dynamic_pointer_cast<SIPAccount>(account);
diff --git a/test/unitTest/plugins/plugins.cpp b/test/unitTest/plugins/plugins.cpp
index 642b2f52ac6882a7e6e3e7d528d3eddacab3245d..820717219b18c9eb7b096b84f7a3d7e76c74de71 100644
--- a/test/unitTest/plugins/plugins.cpp
+++ b/test/unitTest/plugins/plugins.cpp
@@ -210,7 +210,6 @@ PluginsTest::setUp()
             aliceData.accountId_);
         aliceData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
         aliceData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
-        account->enableIceForMedia(true);
     }
 
     // Configure Bob
@@ -220,7 +219,6 @@ PluginsTest::setUp()
             bobData.accountId_);
         bobData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
         bobData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
-        account->enableIceForMedia(true);
     }
 
     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> signalHandlers;
diff --git a/test/unitTest/sip_account/sip_basic_calls.cpp b/test/unitTest/sip_account/sip_basic_calls.cpp
index ebea7bd62efee3952c3c3fb8bfe9deb7917e9de8..44c466b897bd632f8afcb8a152e4e1266727fc4c 100644
--- a/test/unitTest/sip_account/sip_basic_calls.cpp
+++ b/test/unitTest/sip_account/sip_basic_calls.cpp
@@ -402,7 +402,6 @@ SipBasicCallTest::configureTest()
         auto const& account = Manager::instance().getAccount<SIPAccount>(
             callDataMap_["ALICE"].accountId_);
         account->setLocalPort(callDataMap_["ALICE"].listeningPort_);
-        account->enableIceForMedia(true);
     }
 
     {
diff --git a/test/unitTest/sip_account/sip_srtp.cpp b/test/unitTest/sip_account/sip_srtp.cpp
index fbe1d81735c5efbd0cabd439c71d44c811a113d8..2ef865dc9cb1035be363be3618a48cdc09e54990 100644
--- a/test/unitTest/sip_account/sip_srtp.cpp
+++ b/test/unitTest/sip_account/sip_srtp.cpp
@@ -361,7 +361,6 @@ SipSrtpTest::configureTest(CallData& aliceData, CallData& bobData)
         aliceData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
         aliceData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
         account->setLocalPort(aliceData.listeningPort_);
-        account->enableIceForMedia(true);
     }
 
     {