diff --git a/src/api/contactmodel.h b/src/api/contactmodel.h
index 6f54378301845beaf1856a0cced686ba35a9189b..bec615e50a66c6ca49a1e9c9acde030d896b55ae 100644
--- a/src/api/contactmodel.h
+++ b/src/api/contactmodel.h
@@ -75,6 +75,7 @@ public:
      * @throws out_of_range exception if can't find the contact
      */
     const contact::Info getContact(const QString& contactUri) const;
+    ContactInfoMap getSearchResults() const;
     /**
      * get list of banned contacts.
      * @return list of banned contacts uris as string
diff --git a/src/api/conversationmodel.h b/src/api/conversationmodel.h
index 0b36314a3f05d5fa8081770a6206429300f4ce0d..307b7444358e51567d15a523c6962cbdb0e995e5 100644
--- a/src/api/conversationmodel.h
+++ b/src/api/conversationmodel.h
@@ -122,6 +122,26 @@ public:
      * @return a copy of the conversation
      */
     Q_INVOKABLE conversation::Info filteredConversation(unsigned int row) const;
+
+    /**
+     * Get the search results
+     * @return a searchResult
+     */
+    const ConversationQueue&  getAllSearchResults() const;
+
+    /**
+     * Get the conversation at row in the search results
+     * @param  row
+     * @return a copy of the conversation
+     */
+    conversation::Info searchResultForRow(unsigned int row) const;
+
+    /**
+     * Update the searchResults
+     * @param new status
+     */
+    void updateSearchStatus(const QString& status) const;
+
     /**
      * Emit a filterChanged signal to force the client to refresh the filter. For instance
      * this is required when a contact was banned or un-banned.
@@ -324,6 +344,16 @@ Q_SIGNALS:
     void displayedInteractionChanged(const QString& uid, const QString& participantURI,
         const uint64_t previousUid, const uint64_t newdUid) const;
 
+    /**
+     * Emitted when search status changed
+     * @param status
+     */
+    void searchStatusChanged(const QString& status) const;
+    /**
+     * Emitted when search result has been updated
+     */
+    void searchResultUpdated() const;
+
 private:
     std::unique_ptr<ConversationModelPimpl> pimpl_;
 };
diff --git a/src/contactmodel.cpp b/src/contactmodel.cpp
index 707b30c11945cf5df8d4d5b35e24175da3fddff3..3728a040bdba165c0a4bd6516e08f474fe04c4d4 100644
--- a/src/contactmodel.cpp
+++ b/src/contactmodel.cpp
@@ -41,6 +41,7 @@
 #include "callbackshandler.h"
 #include "uri.h"
 #include "vcard.h"
+#include "typedefs.h"
 
 #include "authority/daemon.h"
 #include "authority/storagehelper.h"
@@ -91,10 +92,12 @@ public:
      */
     void searchRingContact(const URI& query);
     void searchSipContact(const URI& query);
+    void jamsSearch(const URI& query);
+
     /**
      * Update temporary item to display a given message about a given uri.
      */
-    void updateTemporaryMessage(const QString& mes, const QString& uri);
+    void updateTemporaryMessage(const QString& mes);
 
     /**
      * Check if equivalent uri exist in contact
@@ -109,7 +112,9 @@ public:
 
     // Containers
     ContactModel::ContactInfoMap contacts;
+    ContactModel::ContactInfoMap searchResult;
     QList<QString> bannedContacts;
+    QString query;
     std::mutex contactsMtx_;
     std::mutex bannedContactsMtx_;
 
@@ -189,6 +194,15 @@ public Q_SLOTS:
      * @param vCard
      */
     void slotProfileReceived(const QString& accountId, const QString& peer, const QString& vCard);
+
+    /**
+     * Listen from daemon to know when a user search completed
+     * @param accountId
+     * @param status
+     * @param query
+     * @param result
+     */
+    void slotUserSearchEnded(const QString& accountId, int status, const QString& query, const VectorMapStringString& result);
 };
 
 using namespace authority;
@@ -348,10 +362,12 @@ const contact::Info
 ContactModel::getContact(const QString& contactUri) const
 {
     std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
-    if (!pimpl_->contacts.contains(contactUri)) {
-        throw std::out_of_range("Contact out of range");
+    if (pimpl_->contacts.contains(contactUri)) {
+        return pimpl_->contacts.value(contactUri);
+    } else if (pimpl_->searchResult.contains(contactUri)) {
+        return pimpl_->searchResult.value(contactUri);
     }
-    return pimpl_->contacts.value(contactUri);
+    throw std::out_of_range("Contact out of range");
 }
 
 const QList<QString>&
@@ -360,13 +376,20 @@ ContactModel::getBannedContacts() const
     return pimpl_->bannedContacts;
 }
 
+ContactModel::ContactInfoMap
+ContactModel::getSearchResults() const
+{
+    return pimpl_->searchResult;
+}
+
 void
 ContactModel::searchContact(const QString& query)
 {
     // always reset temporary contact
-    pimpl_->contacts[""] = {};
+    pimpl_->searchResult.clear();
 
     auto uri = URI(query);
+    pimpl_->query = query;
 
     auto uriScheme = uri.schemeType();
     if (static_cast<int>(uriScheme) > 2 && owner.profileInfo.type == profile::Type::SIP) {
@@ -379,20 +402,20 @@ ContactModel::searchContact(const QString& query)
     if ((uriScheme == URI::SchemeType::SIP || uriScheme == URI::SchemeType::SIPS) && owner.profileInfo.type == profile::Type::SIP) {
         pimpl_->searchSipContact(uri);
     } else if (uriScheme == URI::SchemeType::RING && owner.profileInfo.type == profile::Type::RING) {
-        pimpl_->searchRingContact(uri);
+        bool isJamsAccount = !owner.confProperties.managerUri.isEmpty();
+        if (isJamsAccount)
+            pimpl_->jamsSearch(uri);
+        else
+            pimpl_->searchRingContact(uri);
     } else {
-        pimpl_->updateTemporaryMessage(tr("Bad URI scheme"), uri.full());
+        pimpl_->updateTemporaryMessage(tr("Bad URI scheme"));
     }
 }
 
 void
-ContactModelPimpl::updateTemporaryMessage(const QString& mes, const QString& uri)
+ContactModelPimpl::updateTemporaryMessage(const QString& mes)
 {
-    std::lock_guard<std::mutex> lk(contactsMtx_);
-    auto& temporaryContact = contacts[""];
-    temporaryContact.profileInfo.alias = mes;
-    temporaryContact.profileInfo.type = profile::Type::TEMPORARY;
-    temporaryContact.registeredName = uri;
+    linked.owner.conversationModel->updateSearchStatus(mes);
 }
 
 void
@@ -402,6 +425,7 @@ ContactModelPimpl::searchRingContact(const URI& query)
     if (query.isEmpty()) {
         // This will remove the temporary item
         emit linked.modelUpdated(uriID);
+        updateTemporaryMessage("");
         return;
     }
 
@@ -412,17 +436,30 @@ ContactModelPimpl::searchRingContact(const URI& query)
                 return;
             }
         }
-        auto& temporaryContact = contacts[""];
+        auto& temporaryContact = searchResult[uriID];
         temporaryContact.profileInfo.uri = uriID;
         temporaryContact.profileInfo.alias = uriID;
         temporaryContact.profileInfo.type = profile::Type::TEMPORARY;
+        emit linked.modelUpdated(uriID);
     } else {
-        updateTemporaryMessage(tr("Searching…"), uriID);
+        updateTemporaryMessage(tr("Searching…"));
 
         // Default searching
         ConfigurationManager::instance().lookupName(linked.owner.id, "", uriID);
     }
-    emit linked.modelUpdated(uriID);
+}
+
+void
+ContactModelPimpl::jamsSearch(const URI& query)
+{
+    QString uriID = query.format(URI::Section::USER_INFO | URI::Section::HOSTNAME | URI::Section::PORT);
+    if (query.isEmpty()) {
+        emit linked.modelUpdated(uriID);
+        updateTemporaryMessage("");
+        return;
+    }
+    updateTemporaryMessage(tr("Searching…"));
+    ConfigurationManager::instance().searchUser(linked.owner.id, uriID);
 }
 
 void
@@ -432,10 +469,11 @@ ContactModelPimpl::searchSipContact(const URI& query)
     if (query.isEmpty()) {
         // This will remove the temporary item
         emit linked.modelUpdated(uriID);
+        updateTemporaryMessage("");
         return;
     }
 
-    auto& temporaryContact = contacts[""];
+    auto& temporaryContact = searchResult[query];
     {
         std::lock_guard<std::mutex> lk(contactsMtx_);
         if (contacts.find(uriID) == contacts.end()) {
@@ -469,6 +507,7 @@ ContactModelPimpl::ContactModelPimpl(const ContactModel& linked,
 , behaviorController(behaviorController)
 , callbacksHandler(callbacksHandler)
 {
+    qRegisterMetaType<VectorMapStringString>("VectorMapStringString");
     // Init contacts map
     if (linked.owner.profileInfo.type == profile::Type::SIP)
         fillWithSIPContacts();
@@ -494,6 +533,8 @@ ContactModelPimpl::ContactModelPimpl(const ContactModel& linked,
             this, &ContactModelPimpl::slotNewAccountTransfer);
     connect(&ConfigurationManager::instance(), &ConfigurationManagerInterface::profileReceived,
             this, &ContactModelPimpl::slotProfileReceived);
+    connect(&ConfigurationManager::instance(), &ConfigurationManagerInterface::userSearchEnded,
+            this, &ContactModelPimpl::slotUserSearchEnded);
 }
 
 ContactModelPimpl::~ContactModelPimpl()
@@ -516,6 +557,8 @@ ContactModelPimpl::~ContactModelPimpl()
                this, &ContactModelPimpl::slotNewAccountTransfer);
     disconnect(&ConfigurationManager::instance(), &ConfigurationManagerInterface::profileReceived,
                this, &ContactModelPimpl::slotProfileReceived);
+    disconnect(&ConfigurationManager::instance(), &ConfigurationManagerInterface::userSearchEnded,
+               this, &ContactModelPimpl::slotUserSearchEnded);
 }
 
 bool
@@ -638,6 +681,14 @@ ContactModelPimpl::slotContactAdded(const QString& accountId, const QString& con
             return;
         }
     }
+    //for jams account we already have profile with avatar, use it to save to vCard
+    bool isJamsAccount = !linked.owner.confProperties.managerUri.isEmpty();
+    if (isJamsAccount) {
+        auto result = searchResult.find(contactUri);
+        if (result != searchResult.end()) {
+            storage::createOrUpdateProfile(linked.owner.id, result->profileInfo, true);
+        }
+    }
 
     bool isBanned = false;
 
@@ -660,7 +711,6 @@ ContactModelPimpl::slotContactAdded(const QString& accountId, const QString& con
             addToContacts(contactUri, linked.owner.profileInfo.type, "", false);
         }
     }
-
     if (isBanned) {
         // Update the smartlist
         linked.owner.conversationModel->refreshFilter();
@@ -760,43 +810,48 @@ ContactModelPimpl::slotRegisteredNameFound(const QString& accountId,
 {
     if (accountId != linked.owner.id) return;
 
-    auto& temporaryContact = contacts[""];
     if (status == 0 /* SUCCESS */) {
         std::lock_guard<std::mutex> lk(contactsMtx_);
 
         if (contacts.find(uri) != contacts.end()) {
             // update contact and remove temporary item
             contacts[uri].registeredName = registeredName;
-            temporaryContact = {};
+            searchResult.clear();
         } else {
-            if (temporaryContact.registeredName != uri && temporaryContact.registeredName != registeredName) {
+            if ((query != uri && query != registeredName) || query.isEmpty()) {
                 // we are notified that a previous lookup ended
                 return;
             }
-
-            // update temporary item
+            auto& temporaryContact = searchResult[uri];
             lrc::api::profile::Info profileInfo = {uri, "", "", profile::Type::TEMPORARY};
             temporaryContact = {profileInfo, registeredName, false, false};
         }
     } else {
-        if (temporaryContact.registeredName != uri && temporaryContact.registeredName != registeredName) {
+        {
+            std::lock_guard<std::mutex> lk(contactsMtx_);
+            if (contacts.find(uri) != contacts.end()) {
+                // it was lookup for contact
+                return;
+            }
+        }
+        if ((query != uri && query != registeredName) || query.isEmpty()) {
             // we are notified that a previous lookup ended
             return;
         }
-
         switch (status) {
         case 1 /* INVALID */:
-            updateTemporaryMessage(tr("Invalid ID"), registeredName);
+            updateTemporaryMessage(tr("Invalid ID"));
             break;
         case 2 /* NOT FOUND */:
-            updateTemporaryMessage(tr("Registered name not found"), registeredName);
+            updateTemporaryMessage(tr("Registered name not found"));
             break;
         case 3 /* ERROR */:
-            updateTemporaryMessage(tr("Couldn't lookup…"), registeredName);
+            updateTemporaryMessage(tr("Couldn't lookup…"));
             break;
         }
+        return;
     }
-
+    updateTemporaryMessage("");
     emit linked.modelUpdated(uri);
 }
 
@@ -1009,6 +1064,38 @@ ContactModelPimpl::slotProfileReceived(const QString& accountId, const QString&
     linked.owner.contactModel->addContact(contactInfo);
 }
 
+void
+ContactModelPimpl::slotUserSearchEnded(const QString& accountId, int status, const QString& query, const VectorMapStringString& result)
+{
+    if (query != query) return;
+    if (accountId != linked.owner.id) return;
+    searchResult.clear();
+       switch (status) {
+       case 0:/* SUCCESS */
+           break;
+       case 3:/* ERROR */
+           updateTemporaryMessage("could not find contact matching search");
+           return;
+       default:
+           return;
+    }
+    updateTemporaryMessage("");
+    for (auto& resultInfo : result) {
+        if (contacts.find(resultInfo.value("jamiId")) != contacts.end()) {
+            continue;
+        }
+        profile::Info profileInfo;
+        profileInfo.uri = resultInfo.value("jamiId");
+        profileInfo.type = profile::Type::TEMPORARY;
+        profileInfo.avatar = resultInfo.value("profilePicture");
+        profileInfo.alias = resultInfo.value("firstName") + " " + resultInfo.value("lastName");
+        contact::Info contactInfo;
+        contactInfo.profileInfo = profileInfo;
+        contactInfo.registeredName = resultInfo.value("username");
+        searchResult.insert(profileInfo.uri, contactInfo);
+    }
+    emit linked.modelUpdated(query);
+}
 
 } // namespace lrc
 
diff --git a/src/conversationmodel.cpp b/src/conversationmodel.cpp
index fcb1135a261988121c7faee401dbf5ce2ccd8dfb..19b7c8e6d651a7843b41e86c77d1bc8015bee8ff 100644
--- a/src/conversationmodel.cpp
+++ b/src/conversationmodel.cpp
@@ -76,12 +76,27 @@ public:
      * @return an int.
      */
     int indexOf(const QString& uid) const;
+    /**
+     * return a reference to a conversation with given uid.
+     * @param conversation uid.
+     * @param searchResultIncluded if need to search in contacts and userSearch.
+     * @return a reference to a conversation with given uid.
+     */
+    conversation::Info& getConversation(const QString& uid, const bool searchResultIncluded = false);
+
     /**
      * return a conversation index from conversations or -1 if no index is found.
      * @param uri of the contact to search.
      * @return an int.
      */
     int indexOfContact(const QString& uri) const;
+    /**
+     * return a reference to a conversation with participant.
+     * @param participant uri.
+     * @param searchResultIncluded if need to search in contacts and userSearch.
+     * @return a reference to a conversation with participant.
+     */
+    conversation::Info& getConversationForContact(const QString& uid, const bool searchResultIncluded = false);
     /**
      * Initialize conversations_ and filteredConversations_
      */
@@ -169,6 +184,7 @@ public:
 
     ConversationModel::ConversationQueue conversations; ///< non-filtered conversations
     ConversationModel::ConversationQueue filteredConversations;
+    ConversationModel::ConversationQueue searchResults;
     ConversationModel::ConversationQueue customFilteredConversations;
     QString filter;
     profile::Type typeFilter;
@@ -504,6 +520,12 @@ ConversationModel::getConferenceableConversations(const QString& convId, const Q
     return result;
 }
 
+const ConversationModel::ConversationQueue&
+ConversationModel::getAllSearchResults() const
+{
+    return pimpl_->searchResults;
+}
+
 const ConversationModel::ConversationQueue&
 ConversationModel::getFilteredConversations(const profile::Type& filter, bool forceUpdate, const bool includeBanned) const
 {
@@ -529,13 +551,10 @@ ConversationModel::getFilteredConversations(const profile::Type& filter, bool fo
 conversation::Info
 ConversationModel::getConversationForUID(const QString& uid) const
 {
-    auto conversationIdx = pimpl_->indexOf(uid);
-    if (conversationIdx == -1 || !owner.enabled) {
-        return {};
-    }
     try {
-        return pimpl_->conversations.at(conversationIdx);
-    } catch (...) {
+        return pimpl_->getConversation(uid, true);
+    }
+    catch (const std::out_of_range& e) {
         return {};
     }
 }
@@ -552,87 +571,92 @@ ConversationModel::filteredConversation(const unsigned int row) const
     return conversationInfo;
 }
 
+conversation::Info
+ConversationModel::searchResultForRow(const unsigned int row) const
+{
+    const auto& results = pimpl_->searchResults;
+    if (row >= results.size())
+        return conversation::Info();
+
+    return results.at(row);
+}
+
 void
 ConversationModel::makePermanent(const QString& uid)
 {
-    auto conversationIdx = pimpl_->indexOf(uid);
-    if (conversationIdx == -1 || !owner.enabled)
-        return;
+    try {
+        auto& conversation = pimpl_->getConversation(uid, true);
 
-    auto& conversation = pimpl_->conversations.at(conversationIdx);
-    if (conversation.participants.empty()) {
-        // Should not
-        qDebug() << "ConversationModel::addConversation can't add a conversation with no participant";
-        return;
-    }
+        if (conversation.participants.empty()) {
+            // Should not
+            qDebug() << "ConversationModel::addConversation can't add a conversation with no participant";
+            return;
+        }
 
-    // Send contact request if non used
-    pimpl_->sendContactRequest(conversation.participants.front());
+        // Send contact request if non used
+        pimpl_->sendContactRequest(conversation.participants.front());
+    } catch (const std::out_of_range& e) {
+        qDebug() << "make permanent failed. conversation not found";
+    }
 }
 
 void
 ConversationModel::selectConversation(const QString& uid) const
 {
-    // Get conversation
-    auto conversationIdx = pimpl_->indexOf(uid);
-
-    if (conversationIdx == -1)
-        return;
-
-    if (uid.isEmpty() && owner.contactModel->getContact("").profileInfo.uri.isEmpty()) {
-        // if we select the temporary contact, check if its a valid contact.
-        return;
-    }
+    try {
+        auto& conversation = pimpl_->getConversation(uid, true);
 
-    auto& conversation = pimpl_->conversations.at(conversationIdx);
-    bool callEnded = true;
-    if (!conversation.callId.isEmpty()) {
-        try  {
-            auto call = owner.callModel->getCall(conversation.callId);
-            callEnded = call.status == call::Status::ENDED;
-        } catch (...) {}
-    }
+        bool callEnded = true;
+        if (!conversation.callId.isEmpty()) {
+            try  {
+                auto call = owner.callModel->getCall(conversation.callId);
+                callEnded = call.status == call::Status::ENDED;
+            } catch (...) {}
+        }
 
-    if (not callEnded and not conversation.confId.isEmpty()) {
-        emit pimpl_->behaviorController.showCallView(owner.id, conversation);
-    } else if (callEnded) {
-        emit pimpl_->behaviorController.showChatView(owner.id, conversation);
-    } else {
-        try  {
-            auto call = owner.callModel->getCall(conversation.callId);
-            switch (call.status) {
-            case call::Status::INCOMING_RINGING:
-            case call::Status::OUTGOING_RINGING:
-            case call::Status::CONNECTING:
-            case call::Status::SEARCHING:
-                // We are currently in a call
-                emit pimpl_->behaviorController.showIncomingCallView(owner.id, conversation);
-                break;
-            case call::Status::PAUSED:
-            case call::Status::CONNECTED:
-            case call::Status::IN_PROGRESS:
-                // We are currently receiving a call
-                emit pimpl_->behaviorController.showCallView(owner.id, conversation);
-                break;
-            case call::Status::PEER_BUSY:
-                emit pimpl_->behaviorController.showLeaveMessageView(owner.id, conversation);
-                break;
-            case call::Status::TIMEOUT:
-            case call::Status::TERMINATING:
-            case call::Status::INVALID:
-            case call::Status::INACTIVE:
-                // call just ended
+        if (not callEnded and not conversation.confId.isEmpty()) {
+            emit pimpl_->behaviorController.showCallView(owner.id, conversation);
+        } else if (callEnded) {
+            emit pimpl_->behaviorController.showChatView(owner.id, conversation);
+        } else {
+            try  {
+                auto call = owner.callModel->getCall(conversation.callId);
+                switch (call.status) {
+                case call::Status::INCOMING_RINGING:
+                case call::Status::OUTGOING_RINGING:
+                case call::Status::CONNECTING:
+                case call::Status::SEARCHING:
+                    // We are currently in a call
+                    emit pimpl_->behaviorController.showIncomingCallView(owner.id, conversation);
+                    break;
+                case call::Status::PAUSED:
+                case call::Status::CONNECTED:
+                case call::Status::IN_PROGRESS:
+                    // We are currently receiving a call
+                    emit pimpl_->behaviorController.showCallView(owner.id, conversation);
+                    break;
+                case call::Status::PEER_BUSY:
+                    emit pimpl_->behaviorController.showLeaveMessageView(owner.id, conversation);
+                    break;
+                case call::Status::TIMEOUT:
+                case call::Status::TERMINATING:
+                case call::Status::INVALID:
+                case call::Status::INACTIVE:
+                    // call just ended
+                    emit pimpl_->behaviorController.showChatView(owner.id, conversation);
+                    break;
+                case call::Status::ENDED:
+                default: // ENDED
+                    // nothing to do
+                    break;
+                }
+            } catch (const std::out_of_range&) {
+                // Should not happen
                 emit pimpl_->behaviorController.showChatView(owner.id, conversation);
-                break;
-            case call::Status::ENDED:
-            default: // ENDED
-                // nothing to do
-                break;
             }
-        } catch (const std::out_of_range&) {
-            // Should not happen
-            emit pimpl_->behaviorController.showChatView(owner.id, conversation);
         }
+    } catch (const std::out_of_range& e) {
+        qDebug() << "select conversation failed. conversation not exists";
     }
 }
 
@@ -672,66 +696,62 @@ ConversationModel::deleteObsoleteHistory(int days)
 void
 ConversationModelPimpl::placeCall(const QString& uid, bool isAudioOnly)
 {
-    auto conversationIdx = indexOf(uid);
-
-    if (conversationIdx == -1 || !linked.owner.enabled)
-        return;
-
-    auto& conversation = conversations.at(conversationIdx);
-    if (conversation.participants.empty()) {
-        // Should not
-        qDebug() << "ConversationModel::placeCall can't call a conversation without participant";
-        return;
-    }
+    try {
+        auto& conversation = getConversation(uid, true);
+        if (conversation.participants.empty()) {
+            // Should not
+            qDebug() << "ConversationModel::placeCall can't call a conversation without participant";
+            return;
+        }
 
-    // Disallow multiple call
-    if (!conversation.callId.isEmpty()) {
-        try  {
-            auto call = linked.owner.callModel->getCall(conversation.callId);
-            switch (call.status) {
-                case call::Status::INCOMING_RINGING:
-                case call::Status::OUTGOING_RINGING:
-                case call::Status::CONNECTING:
-                case call::Status::SEARCHING:
-                case call::Status::PAUSED:
-                case call::Status::IN_PROGRESS:
-                case call::Status::CONNECTED:
-                    return;
-                case call::Status::INVALID:
-                case call::Status::INACTIVE:
-                case call::Status::ENDED:
-                case call::Status::PEER_BUSY:
-                case call::Status::TIMEOUT:
-                case call::Status::TERMINATING:
-                default:
-                    break;
+        // Disallow multiple call
+        if (!conversation.callId.isEmpty()) {
+            try  {
+                auto call = linked.owner.callModel->getCall(conversation.callId);
+                switch (call.status) {
+                    case call::Status::INCOMING_RINGING:
+                    case call::Status::OUTGOING_RINGING:
+                    case call::Status::CONNECTING:
+                    case call::Status::SEARCHING:
+                    case call::Status::PAUSED:
+                    case call::Status::IN_PROGRESS:
+                    case call::Status::CONNECTED:
+                        return;
+                    case call::Status::INVALID:
+                    case call::Status::INACTIVE:
+                    case call::Status::ENDED:
+                    case call::Status::PEER_BUSY:
+                    case call::Status::TIMEOUT:
+                    case call::Status::TERMINATING:
+                    default:
+                        break;
+                }
+            } catch (const std::out_of_range&) {
             }
-        } catch (const std::out_of_range&) {
         }
-    }
 
-    auto convId = uid;
+        auto convId = uid;
 
-    auto participant = conversation.participants.front();
-    bool isTemporary = participant.isEmpty();
-    auto contactInfo = linked.owner.contactModel->getContact(participant);
-    auto uri = contactInfo.profileInfo.uri;
+        auto participant = conversation.participants.front();
+        bool isTemporary = participant == convId;
+        auto contactInfo = linked.owner.contactModel->getContact(participant);
+        auto uri = contactInfo.profileInfo.uri;
 
-    if (uri.isEmpty())
-        return; // Incorrect item
+        if (uri.isEmpty())
+            return; // Incorrect item
 
-    // Don't call banned contact
-    if (contactInfo.isBanned) {
-        qDebug() << "ContactModel::placeCall: denied, contact is banned";
-        return;
-    }
+        // Don't call banned contact
+        if (contactInfo.isBanned) {
+            qDebug() << "ContactModel::placeCall: denied, contact is banned";
+            return;
+        }
 
-    if (linked.owner.profileInfo.type != profile::Type::SIP) {
-        uri = "ring:" + uri; // Add the ring: before or it will fail.
-    }
+        if (linked.owner.profileInfo.type != profile::Type::SIP) {
+            uri = "ring:" + uri; // Add the ring: before or it will fail.
+        }
 
-    auto cb = std::function<void(QString)>(
-        [this, isTemporary, uri, isAudioOnly, &conversation](QString convId) {
+        auto cb = std::function<void(QString)>(
+                                               [this, isTemporary, uri, isAudioOnly, &conversation](QString convId) {
             int contactIndex;
             if (isTemporary && (contactIndex = indexOfContact(convId)) < 0) {
                 qDebug() << "Can't place call: Other participant is not a contact (removed while placing call ?)";
@@ -751,22 +771,25 @@ ConversationModelPimpl::placeCall(const QString& uid, bool isAudioOnly)
             emit behaviorController.showIncomingCallView(linked.owner.id, newConv);
         });
 
-    if (isTemporary) {
-        QMetaObject::Connection* const connection = new QMetaObject::Connection;
-        *connection = connect(&this->linked, &ConversationModel::conversationReady,
-            [cb, connection](QString convId) {
+        if (isTemporary) {
+            QMetaObject::Connection* const connection = new QMetaObject::Connection;
+            *connection = connect(&this->linked, &ConversationModel::conversationReady,
+                                  [cb, connection](QString convId) {
                 cb(convId);
                 QObject::disconnect(*connection);
                 if (connection) {
                     delete connection;
                 }
             });
-    }
+        }
 
-    sendContactRequest(participant);
+        sendContactRequest(participant);
 
-    if (!isTemporary) {
-        cb(convId);
+        if (!isTemporary) {
+            cb(convId);
+        }
+    } catch (const std::out_of_range& e) {
+        qDebug() << "could not place call to not existing conversation";
     }
 }
 
@@ -785,31 +808,28 @@ ConversationModel::placeCall(const QString& uid)
 void
 ConversationModel::sendMessage(const QString& uid, const QString& body)
 {
-    // FIXME potential race condition between index check and at() call
-    auto conversationIdx = pimpl_->indexOf(uid);
-    if (conversationIdx == -1 || !owner.enabled)
-        return;
-
-    auto& conversation = pimpl_->conversations.at(conversationIdx);
+    try {
+        auto& conversation = pimpl_->getConversation(uid, true);
 
-    if (conversation.participants.empty()) {
-        // Should not
-        qDebug() << "ConversationModel::sendMessage can't send a interaction to a conversation with no participant";
-        return;
-    }
+        if (conversation.participants.empty()) {
+            // Should not
+            qDebug() << "ConversationModel::sendMessage can't send a interaction to a conversation with no participant";
+            return;
+        }
 
-    auto convId = uid;
-    bool isTemporary = conversation.participants.front() == "";
+        auto convId = uid;
+        //for temporary contact conversation id is the same as participant uri
+        bool isTemporary = conversation.participants.front() == uid;
 
-    /* Make a copy of participants list: if current conversation is temporary,
-       it might me destroyed while we are reading it */
-    const auto participants = conversation.participants;
+        /* Make a copy of participants list: if current conversation is temporary,
+         it might me destroyed while we are reading it */
+        const auto participants = conversation.participants;
 
-    auto cb = std::function<void(QString)>(
-        [this, isTemporary, body, &conversation](QString convId) {
+        auto cb = std::function<void(QString)>(
+                                               [this, isTemporary, body, &conversation](QString convId) {
             /* Now we should be able to retrieve the final conversation, in case the previous
-               one was temporary */
-               // FIXME potential race condition between index check and at() call
+             one was temporary */
+            // FIXME potential race condition between index check and at() call
             int contactIndex;
             if (isTemporary && (contactIndex = pimpl_->indexOfContact(convId)) < 0) {
                 qDebug() << "Can't send message: Other participant is not a contact";
@@ -882,33 +902,36 @@ ConversationModel::sendMessage(const QString& uid, const QString& body)
             emit modelSorted();
         });
 
-    if (isTemporary) {
-        QMetaObject::Connection* const connection = new QMetaObject::Connection;
-        *connection = connect(this, &ConversationModel::conversationReady,
-            [cb, connection](QString convId) {
+        if (isTemporary) {
+            QMetaObject::Connection* const connection = new QMetaObject::Connection;
+            *connection = connect(this, &ConversationModel::conversationReady,
+                                  [cb, connection](QString convId) {
                 cb(convId);
                 QObject::disconnect(*connection);
                 if (connection) {
                     delete connection;
                 }
             });
-    }
+        }
 
-    /* Check participants list, send contact request if needed.
-       NOTE: conferences are not implemented yet, so we have only one participant */
-    for (const auto& participant: participants) {
-        auto contactInfo = owner.contactModel->getContact(participant);
+        /* Check participants list, send contact request if needed.
+         NOTE: conferences are not implemented yet, so we have only one participant */
+        for (const auto& participant: participants) {
+            auto contactInfo = owner.contactModel->getContact(participant);
 
-        if (contactInfo.isBanned) {
-            qDebug() << "ContactModel::sendMessage: denied, contact is banned";
-            return;
-        }
+            if (contactInfo.isBanned) {
+                qDebug() << "ContactModel::sendMessage: denied, contact is banned";
+                return;
+            }
 
-        pimpl_->sendContactRequest(participant);
-    }
+            pimpl_->sendContactRequest(participant);
+        }
 
-    if (!isTemporary) {
-        cb(convId);
+        if (!isTemporary) {
+            cb(convId);
+        }
+    }catch (const std::out_of_range& e) {
+        qDebug() << "could not send message to not existing conversation";
     }
 }
 
@@ -919,12 +942,19 @@ ConversationModel::refreshFilter()
     emit filterChanged();
 }
 
+void
+ConversationModel::updateSearchStatus(const QString& status) const
+{
+    emit searchStatusChanged(status);
+}
+
 void
 ConversationModel::setFilter(const QString& filter)
 {
     pimpl_->filter = filter;
     pimpl_->dirtyConversations = {true, true};
-    // Will update the temporary contact in the contactModel
+    pimpl_->searchResults.clear();
+    emit searchResultUpdated();
     owner.contactModel->searchContact(filter);
     emit filterChanged();
 }
@@ -1503,6 +1533,10 @@ ConversationModelPimpl::slotContactAdded(const QString& contactUri)
     if (indexOf(profileInfo.uri) >= 0) {
         conversations.erase(conversations.begin() + indexOf(profileInfo.uri));
     }
+    for (unsigned int i = 0; i < searchResults.size(); ++i) {
+        if (searchResults.at(i).uid == profileInfo.uri)
+            searchResults.erase(searchResults.begin() + i);
+    }
 
     sortConversations();
     emit linked.conversationReady(profileInfo.uri);
@@ -1564,74 +1598,36 @@ ConversationModelPimpl::slotContactRemoved(const QString& uri)
 void
 ConversationModelPimpl::slotContactModelUpdated(const QString& uri, bool needsSorted)
 {
-    // We don't create newConversationItem if we already filter on pending
-    conversation::Info newConversationItem;
-    if (!filter.isEmpty()) {
-        // Create a conversation with the temporary item
-        conversation::Info conversationInfo;
-        auto& temporaryContact = linked.owner.contactModel->getContact("");
-        conversationInfo.uid = temporaryContact.profileInfo.uri;
-        conversationInfo.participants.push_back("");
-        conversationInfo.accountId = linked.owner.id;
-
-        // if temporary contact is already present, its alias is not empty (namely "Searching ..."),
-        // or its registeredName is set because it was found on the nameservice.
-        if (not temporaryContact.profileInfo.alias.isEmpty() || not temporaryContact.registeredName.isEmpty()) {
-            if (!conversations.empty()) {
-                auto firstContactUri = conversations.front().participants.front();
-                //if first conversation has uri it is already a contact
-                // then we must add temporary item
-                if (not firstContactUri.isEmpty()) {
-                    conversations.emplace_front(conversationInfo);
-                } else if (not conversationInfo.uid.isEmpty()) {
-                    // If firstContactUri is empty it means that we have to update
-                    // this element as it is the temporary.
-                    // Only when we have found an uri.
-                    conversations.front() = conversationInfo;
-                } else if (not conversations.front().uid.isEmpty()) {
-                    //update conversation when uri not found
-                    //but conversation have uri from previous search
-                    conversations.front() = conversationInfo;
-                }
-            } else {
-                // no conversation, add temporaryItem
-                conversations.emplace_front(conversationInfo);
-            }
+    //presence updated
+    if (!needsSorted) {
+        try {
+            auto& conversation = getConversationForContact(uri, true);
             dirtyConversations = {true, true};
-            if (needsSorted) {
-                emit linked.modelSorted();
-            } else {
-                emit linked.conversationUpdated(conversations.front().uid);
-            }
-            return;
+            emit linked.conversationUpdated(conversation.uid);
+        } catch (std::out_of_range&) {
+            qDebug() << "contact updated for not existing conversation";
         }
-    } else {
-        // No filter, so we can remove the newConversationItem
-        if (!conversations.empty()) {
-            auto firstContactUri = conversations.front().participants.front();
+        return;
+    }
 
-            if (firstContactUri.isEmpty() && needsSorted) {
-                conversations.pop_front();
-                dirtyConversations = {true, true};
-                emit linked.modelSorted();
-                return;
-            }
+    if (filter.isEmpty()) {
+        if (searchResults.empty()) {
+            return;
         }
+        searchResults.clear();
+        emit linked.searchResultUpdated();
+        return;
     }
-    // trigger dirtyConversation in all cases to flush emptied temporary element due to filtered contact present in list
-    // TL:DR : avoid duplicates and empty elements
-    dirtyConversations = {true, true};
-    int index = indexOfContact(uri);
-    if (index != -1) {
-        if (!conversations.empty() && conversations.front().participants.front().isEmpty() &&
-            needsSorted) {
-            // In this case, contact is present in list, so temporary item does not longer exists
-            emit linked.modelSorted();
-        } else {
-            // In this case, a presence is updated
-            emit linked.conversationUpdated(conversations.at(index).uid);
-        }
+    searchResults.clear();
+    auto users = linked.owner.contactModel->getSearchResults();
+    for (auto& user : users) {
+        conversation::Info conversationInfo;
+        conversationInfo.uid = user.profileInfo.uri;
+        conversationInfo.participants.push_back(user.profileInfo.uri);
+        conversationInfo.accountId = linked.owner.id;
+        searchResults.emplace_front(conversationInfo);
     }
+    emit linked.searchResultUpdated();
 }
 
 void
@@ -1695,6 +1691,38 @@ ConversationModelPimpl::indexOf(const QString& uid) const
     return -1;
 }
 
+conversation::Info&
+ConversationModelPimpl::getConversation(const QString& uid, const bool searchResultIncluded)
+{
+    for (unsigned int i = 0; i < conversations.size(); ++i) {
+        if (conversations.at(i).uid == uid) return conversations.at(i);
+    }
+
+    if (searchResultIncluded) {
+        for (unsigned int i = 0; i < searchResults.size(); ++i) {
+            if (searchResults.at(i).uid == uid) return searchResults.at(i);
+        }
+    }
+    throw std::out_of_range("Conversation out of range");
+}
+
+conversation::Info&
+ConversationModelPimpl::getConversationForContact(const QString& uri, const bool searchResultIncluded)
+{
+    for (unsigned int i = 0; i < conversations.size(); ++i) {
+        if (conversations.at(i).participants.front() == uri)
+            return conversations.at(i);
+    }
+
+    if (searchResultIncluded) {
+        for (unsigned int i = 0; i < searchResults.size(); ++i) {
+            if (searchResults.at(i).participants.front() == uri)
+                return searchResults.at(i);
+        }
+    }
+    throw std::out_of_range("Conversation out of range");
+}
+
 int
 ConversationModelPimpl::indexOfContact(const QString& uri) const
 {
@@ -2045,23 +2073,22 @@ ConversationModel::sendFile(const QString& convUid,
                             const QString& path,
                             const QString& filename)
 {
-    auto conversationIdx = pimpl_->indexOf(convUid);
-    if (conversationIdx == -1 || !owner.enabled)
-        return;
+    try {
+        auto& conversation = pimpl_->getConversation(convUid, true);
 
-    const auto peerUri = pimpl_->conversations[conversationIdx].participants.front();
-    bool isTemporary = peerUri.isEmpty();
+        const auto peerUri = conversation.participants.front();
+        bool isTemporary = peerUri == convUid;
 
-    /* It is necessary to make a copy of convUid since it may very well point to
-       a field in the temporary conversation, which is going to be destroyed by
-       slotContactAdded() (indirectly triggered by sendContactrequest(). Not doing
-       so may result in use after free/crash. */
-    auto convUidCopy = convUid;
+        /* It is necessary to make a copy of convUid since it may very well point to
+         a field in the temporary conversation, which is going to be destroyed by
+         slotContactAdded() (indirectly triggered by sendContactrequest(). Not doing
+         so may result in use after free/crash. */
+        auto convUidCopy = convUid;
 
-    pimpl_->sendContactRequest(peerUri);
+        pimpl_->sendContactRequest(peerUri);
 
-    auto cb = std::function<void(QString)>(
-        [this, isTemporary, peerUri, path, filename](QString convId) {
+        auto cb = std::function<void(QString)>(
+                                               [this, isTemporary, peerUri, path, filename](QString convId) {
             int contactIndex;
             if (isTemporary && (contactIndex = pimpl_->indexOfContact(convId)) < 0) {
                 qDebug() << "Can't send file: Other participant is not a contact (removed while sending file ?)";
@@ -2083,18 +2110,21 @@ ConversationModel::sendFile(const QString& convUid,
                                                         filename);
         });
 
-    if (isTemporary) {
-        QMetaObject::Connection* const connection = new QMetaObject::Connection;
-        *connection = connect(this, &ConversationModel::conversationReady,
-            [cb, connection](QString convId) {
+        if (isTemporary) {
+            QMetaObject::Connection* const connection = new QMetaObject::Connection;
+            *connection = connect(this, &ConversationModel::conversationReady,
+                                  [cb, connection](QString convId) {
                 cb(convId);
                 QObject::disconnect(*connection);
                 if (connection) {
                     delete connection;
                 }
             });
-    } else {
-        cb(convUidCopy);
+        } else {
+            cb(convUidCopy);
+        }
+    } catch (const std::out_of_range& e) {
+        qDebug() << "could not send file to not existing conversation";
     }
 }
 
diff --git a/src/qtwrapper/configurationmanager_wrap.h b/src/qtwrapper/configurationmanager_wrap.h
index 5fa19498fd6d1d2fc0424ba6bf9085ff88cb46a7..1d72acb848b437fafe72596a96bf2ba1e22d9c57 100644
--- a/src/qtwrapper/configurationmanager_wrap.h
+++ b/src/qtwrapper/configurationmanager_wrap.h
@@ -184,6 +184,10 @@ public:
                 [this](const std::string& account_id, const std::string& from, int status) {
                     Q_EMIT this->composingStatusChanged(QString(account_id.c_str()), QString(from.c_str()), status > 0 ? true : false);
                 }),
+            exportable_callback<ConfigurationSignal::UserSearchEnded>(
+                [this](const std::string& account_id, int status, const std::string& query, const std::vector<std::map<std::string, std::string>>& results) {
+                    Q_EMIT this->userSearchEnded(QString(account_id.c_str()), status, QString(query.c_str()), convertVecMap(results));
+                }),
         };
 
         dataXferHandlers = {
@@ -725,6 +729,10 @@ public Q_SLOTS: // METHODS
         return DRing::setMessageDisplayed(accountId.toStdString(), contactId.toStdString(), messageId.toStdString(), status);
     }
 
+    bool searchUser(const QString& accountId, const QString& query) {
+        return DRing::searchUser(accountId.toStdString(), query.toStdString());
+    }
+
 Q_SIGNALS: // SIGNALS
     void volumeChanged(const QString& device, double value);
     void accountsChanged();
@@ -758,6 +766,7 @@ Q_SIGNALS: // SIGNALS
     void accountProfileReceived(const QString& accountId, const QString& displayName, const QString& userPhoto);
     void debugMessageReceived(const QString& message);
     void composingStatusChanged(const QString& accountId, const QString& contactId, bool isComposing);
+    void userSearchEnded(const QString& accountId, int status, const QString& query, VectorMapStringString results);
 };
 
 namespace org { namespace ring { namespace Ring {