diff --git a/CHANGES.md b/CHANGES.md
index 1ffb6bcad0..b96337097b 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,7 +2,7 @@ Changes in Element 1.0.6 (2020-XX-XX)
 ===================================================
 
 Features ✨:
- -
+ - List phone numbers and emails added to the Matrix account, and add Email to account (#44)
 
 Improvements 🙌:
  - You can now join room through permalink and within room directory search
diff --git a/docs/add_email.md b/docs/add_email.md
index 64227418a3..06b01b3026 100644
--- a/docs/add_email.md
+++ b/docs/add_email.md
@@ -12,7 +12,7 @@
 }
 ```
 
-### The email is already adding to an account
+### The email is already added to an account
 
 400
 
@@ -84,6 +84,8 @@ User clicks on CONTINUE
 
 POST https://homeserver.org/_matrix/client/r0/account/3pid/add
 
+TODO: Remove "identifier"?
+
 ```json
 {
   "sid": "bxyDHuJKsdkjMlTJ",
diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
index 45efd125ee..55ede52c0c 100644
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
+++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
@@ -18,9 +18,13 @@
 package org.matrix.android.sdk.rx
 
 import androidx.paging.PagedList
+import io.reactivex.Observable
+import io.reactivex.Single
+import io.reactivex.functions.Function3
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
@@ -43,10 +47,6 @@ import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
 import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
-import io.reactivex.Observable
-import io.reactivex.Single
-import io.reactivex.functions.Function3
 
 class RxSession(private val session: Session) {
 
@@ -110,6 +110,11 @@ class RxSession(private val session: Session) {
                 .startWithCallable { session.getThreePids() }
     }
 
+    fun livePendingThreePIds(): Observable<List<ThreePid>> {
+        return session.getPendingThreePidsLive().asObservable()
+                .startWithCallable { session.getPendingThreePids() }
+    }
+
     fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
         session.createRoom(roomParams, it)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt
index 449c670983..95f142e877 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt
@@ -83,4 +83,32 @@ interface ProfileService {
      * @param refreshData set to true to fetch data from the homeserver
      */
     fun getThreePidsLive(refreshData: Boolean): LiveData<List<ThreePid>>
+
+    /**
+     * Get the pending 3Pids, i.e. ThreePids that have requested a token, but not yet validated by the user.
+     */
+    fun getPendingThreePids(): List<ThreePid>
+
+    /**
+     * Get the pending 3Pids Live
+     */
+    fun getPendingThreePidsLive(): LiveData<List<ThreePid>>
+
+    /**
+     * Add a 3Pids. This is the first step to add a ThreePid to an account. Then the threePid will be added to the pending threePid list.
+     */
+    fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable
+
+    /**
+     * Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid
+     */
+    fun finalizeAddingThreePid(threePid: ThreePid,
+                               uiaSession: String?,
+                               accountPassword: String?,
+                               matrixCallback: MatrixCallback<Unit>): Cancelable
+
+    /**
+     * Delete a 3Pids.
+     */
+    fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index 7d2a4ea581..00c4fdac84 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -20,6 +20,7 @@ package org.matrix.android.sdk.internal.database
 import io.realm.DynamicRealm
 import io.realm.RealmMigration
 import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
+import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
 import timber.log.Timber
 import javax.inject.Inject
@@ -32,6 +33,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
         if (oldVersion <= 0) migrateTo1(realm)
         if (oldVersion <= 1) migrateTo2(realm)
         if (oldVersion <= 2) migrateTo3(realm)
+        if (oldVersion <= 3) migrateTo4(realm)
     }
 
     private fun migrateTo1(realm: DynamicRealm) {
@@ -63,4 +65,17 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
                     obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
                 }
     }
+
+    private fun migrateTo4(realm: DynamicRealm) {
+        Timber.d("Step 3 -> 4")
+        realm.schema.create("PendingThreePidEntity")
+                .addField(PendingThreePidEntityFields.CLIENT_SECRET, String::class.java)
+                .setRequired(PendingThreePidEntityFields.CLIENT_SECRET, true)
+                .addField(PendingThreePidEntityFields.EMAIL, String::class.java)
+                .addField(PendingThreePidEntityFields.MSISDN, String::class.java)
+                .addField(PendingThreePidEntityFields.SEND_ATTEMPT, Int::class.java)
+                .setRequired(PendingThreePidEntityFields.SEND_ATTEMPT, true)
+                .addField(PendingThreePidEntityFields.SID, String::class.java)
+                .setRequired(PendingThreePidEntityFields.SID, true)
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PendingThreePidEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PendingThreePidEntity.kt
new file mode 100644
index 0000000000..bf2f11dedd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PendingThreePidEntity.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.model
+
+import io.realm.RealmObject
+
+/**
+ * This class is used to store pending threePid data, when user wants to add a threePid to his account
+ */
+internal open class PendingThreePidEntity(
+        var email: String? = null,
+        var msisdn: String? = null,
+        var clientSecret: String = "",
+        var sendAttempt: Int = 0,
+        var sid: String = ""
+) : RealmObject()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
index ea466db352..2c45cfcdbf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
@@ -36,6 +36,7 @@ import io.realm.annotations.RealmModule
             RoomSummaryEntity::class,
             RoomTagEntity::class,
             SyncEntity::class,
+            PendingThreePidEntity::class,
             UserEntity::class,
             IgnoredUserEntity::class,
             BreadcrumbsEntity::class,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddEmailBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddEmailBody.kt
new file mode 100644
index 0000000000..ff81ad6a5c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddEmailBody.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.matrix.android.sdk.internal.session.profile
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class AddEmailBody(
+        /**
+         * Required. A unique string generated by the client, and used to identify the validation attempt.
+         * It must be a string consisting of the characters [0-9a-zA-Z.=_-]. Its length must not exceed
+         * 255 characters and it must not be empty.
+         */
+        @Json(name = "client_secret")
+        val clientSecret: String,
+
+        /**
+         * Required. The email address to validate.
+         */
+        @Json(name = "email")
+        val email: String,
+
+        /**
+         * Required. The server will only send an email if the send_attempt is a number greater than the most
+         * recent one which it has seen, scoped to that email + client_secret pair. This is to avoid repeatedly
+         * sending the same email in the case of request retries between the POSTing user and the identity server.
+         * The client should increment this value if they desire a new email (e.g. a reminder) to be sent.
+         * If they do not, the server should respond with success but not resend the email.
+         */
+        @Json(name = "send_attempt")
+        val sendAttempt: Int
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidResponse.kt
new file mode 100644
index 0000000000..109b3d5343
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidResponse.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.matrix.android.sdk.internal.session.profile
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class AddThreePidResponse(
+        /**
+         * Required. The session ID. Session IDs are opaque strings that must consist entirely
+         * of the characters [0-9a-zA-Z.=_-]. Their length must not exceed 255 characters and they must not be empty.
+         */
+        @Json(name = "sid")
+        val sid: String
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt
new file mode 100644
index 0000000000..75c829c785
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.profile
+
+import com.zhuinden.monarchy.Monarchy
+import org.greenrobot.eventbus.EventBus
+import org.matrix.android.sdk.api.session.identity.ThreePid
+import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.task.Task
+import org.matrix.android.sdk.internal.util.awaitTransaction
+import java.util.UUID
+import javax.inject.Inject
+
+internal abstract class AddThreePidTask : Task<AddThreePidTask.Params, Unit> {
+    data class Params(
+            val threePid: ThreePid
+    )
+}
+
+internal class DefaultAddThreePidTask @Inject constructor(
+        private val profileAPI: ProfileAPI,
+        @SessionDatabase private val monarchy: Monarchy,
+        private val pendingThreePidMapper: PendingThreePidMapper,
+        private val eventBus: EventBus) : AddThreePidTask() {
+
+    override suspend fun execute(params: Params) {
+        val clientSecret = UUID.randomUUID().toString()
+        val sendAttempt = 1
+        val result = when (params.threePid) {
+            is ThreePid.Email ->
+                executeRequest<AddThreePidResponse>(eventBus) {
+                    val body = AddEmailBody(
+                            email = params.threePid.email,
+                            sendAttempt = sendAttempt,
+                            clientSecret = clientSecret
+                    )
+                    apiCall = profileAPI.addEmail(body)
+                }
+            is ThreePid.Msisdn -> TODO()
+        }
+
+        // Store as a pending three pid
+        monarchy.awaitTransaction { realm ->
+            PendingThreePid(
+                    threePid = params.threePid,
+                    clientSecret = clientSecret,
+                    sendAttempt = sendAttempt,
+                    sid = result.sid
+            )
+                    .let { pendingThreePidMapper.map(it) }
+                    .let { realm.copyToRealm(it) }
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
index 633b047994..cd81496814 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.profile.ProfileService
 import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.Optional
+import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
 import org.matrix.android.sdk.internal.database.model.UserThreePidEntity
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.session.content.FileUploader
@@ -44,6 +45,10 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
                                                          private val getProfileInfoTask: GetProfileInfoTask,
                                                          private val setDisplayNameTask: SetDisplayNameTask,
                                                          private val setAvatarUrlTask: SetAvatarUrlTask,
+                                                         private val addThreePidTask: AddThreePidTask,
+                                                         private val finalizeAddingThreePidTask: FinalizeAddingThreePidTask,
+                                                         private val deleteThreePidTask: DeleteThreePidTask,
+                                                         private val pendingThreePidMapper: PendingThreePidMapper,
                                                          private val fileUploader: FileUploader) : ProfileService {
 
     override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
@@ -116,9 +121,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
     override fun getThreePidsLive(refreshData: Boolean): LiveData<List<ThreePid>> {
         if (refreshData) {
             // Force a refresh of the values
-            refreshUserThreePidsTask
-                    .configureWith()
-                    .executeBy(taskExecutor)
+            refreshThreePids()
         }
 
         return monarchy.findAllMappedWithChanges(
@@ -126,6 +129,69 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
                 { it.asDomain() }
         )
     }
+
+    private fun refreshThreePids() {
+        refreshUserThreePidsTask
+                .configureWith()
+                .executeBy(taskExecutor)
+    }
+
+    override fun getPendingThreePids(): List<ThreePid> {
+        return monarchy.fetchAllMappedSync(
+                { it.where<PendingThreePidEntity>() },
+                { pendingThreePidMapper.map(it).threePid }
+        )
+    }
+
+    override fun getPendingThreePidsLive(): LiveData<List<ThreePid>> {
+        return monarchy.findAllMappedWithChanges(
+                { it.where<PendingThreePidEntity>() },
+                { pendingThreePidMapper.map(it).threePid }
+        )
+    }
+
+    override fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable {
+        return addThreePidTask
+                .configureWith(AddThreePidTask.Params(threePid)) {
+                    callback = matrixCallback
+                }
+                .executeBy(taskExecutor)
+    }
+
+    override fun finalizeAddingThreePid(threePid: ThreePid,
+                                        uiaSession: String?,
+                                        accountPassword: String?,
+                                        matrixCallback: MatrixCallback<Unit>): Cancelable {
+        return finalizeAddingThreePidTask
+                .configureWith(FinalizeAddingThreePidTask.Params(threePid, uiaSession, accountPassword)) {
+                    callback = alsoRefresh(matrixCallback)
+                }
+                .executeBy(taskExecutor)
+    }
+
+    /**
+     * Wrap the callback to fetch 3Pids from the server in case of success
+     */
+    private fun alsoRefresh(callback: MatrixCallback<Unit>): MatrixCallback<Unit> {
+        return object : MatrixCallback<Unit> {
+            override fun onFailure(failure: Throwable) {
+                callback.onFailure(failure)
+            }
+
+            override fun onSuccess(data: Unit) {
+                refreshThreePids()
+                callback.onSuccess(data)
+            }
+        }
+    }
+
+    override fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable {
+        return deleteThreePidTask
+                .configureWith(DeleteThreePidTask.Params(threePid)) {
+                    callback = alsoRefresh(matrixCallback)
+                }
+                .executeBy(taskExecutor)
+    }
 }
 
 private fun UserThreePidEntity.asDomain(): ThreePid {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidBody.kt
new file mode 100644
index 0000000000..e7d4568f8b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidBody.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.matrix.android.sdk.internal.session.profile
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class DeleteThreePidBody(
+        /**
+         * Required. The medium of the third party identifier being removed. One of: ["email", "msisdn"]
+         */
+        @Json(name = "medium") val medium: String,
+        /**
+         * Required. The third party address being removed.
+         */
+        @Json(name = "address") val address: String
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidResponse.kt
new file mode 100644
index 0000000000..3817277a9d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidResponse.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.matrix.android.sdk.internal.session.profile
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class DeleteThreePidResponse(
+        /**
+         * Required. An indicator as to whether or not the homeserver was able to unbind the 3PID from
+         * the identity server. success indicates that the identity server has unbound the identifier
+         * whereas no-support indicates that the identity server refuses to support the request or the
+         * homeserver was not able to determine an identity server to unbind from. One of: ["no-support", "success"]
+         */
+        @Json(name = "id_server_unbind_result")
+        val idServerUnbindResult: String? = null
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt
new file mode 100644
index 0000000000..69ff7d82da
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.profile
+
+import org.greenrobot.eventbus.EventBus
+import org.matrix.android.sdk.api.session.identity.ThreePid
+import org.matrix.android.sdk.api.session.identity.toMedium
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal abstract class DeleteThreePidTask : Task<DeleteThreePidTask.Params, Unit> {
+    data class Params(
+            val threePid: ThreePid
+    )
+}
+
+internal class DefaultDeleteThreePidTask @Inject constructor(
+        private val profileAPI: ProfileAPI,
+        private val eventBus: EventBus) : DeleteThreePidTask() {
+
+    override suspend fun execute(params: Params) {
+        executeRequest<DeleteThreePidResponse>(eventBus) {
+            val body = DeleteThreePidBody(
+                    medium = params.threePid.toMedium(),
+                    address = params.threePid.value
+            )
+            apiCall = profileAPI.deleteThreePid(body)
+        }
+
+        // We do not really care about the result for the moment
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddThreePidBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddThreePidBody.kt
new file mode 100644
index 0000000000..73e9b39cea
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddThreePidBody.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.matrix.android.sdk.internal.session.profile
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
+
+@JsonClass(generateAdapter = true)
+internal data class FinalizeAddThreePidBody(
+        /**
+         * Required. The client secret used in the session with the homeserver.
+         */
+        @Json(name = "client_secret")
+        val clientSecret: String,
+
+        /**
+         * Required. The session identifier given by the homeserver.
+         */
+        @Json(name = "sid")
+        val sid: String,
+
+        /**
+         * Additional authentication information for the user-interactive authentication API.
+         */
+        @Json(name = "auth")
+        val auth: UserPasswordAuth?
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt
new file mode 100644
index 0000000000..e6f4141ab1
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.profile
+
+import com.zhuinden.monarchy.Monarchy
+import org.greenrobot.eventbus.EventBus
+import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
+import org.matrix.android.sdk.api.session.identity.ThreePid
+import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
+import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
+import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
+import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.task.Task
+import org.matrix.android.sdk.internal.util.awaitTransaction
+import javax.inject.Inject
+
+internal abstract class FinalizeAddingThreePidTask : Task<FinalizeAddingThreePidTask.Params, Unit> {
+    data class Params(
+            val threePid: ThreePid,
+            val session: String?,
+            val accountPassword: String?
+    )
+}
+
+internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
+        private val profileAPI: ProfileAPI,
+        @SessionDatabase private val monarchy: Monarchy,
+        private val pendingThreePidMapper: PendingThreePidMapper,
+        @UserId private val userId: String,
+        private val eventBus: EventBus) : FinalizeAddingThreePidTask() {
+
+    override suspend fun execute(params: Params) {
+        // Get the required pending data
+        val pendingThreePids = monarchy.fetchAllMappedSync(
+                { it.where(PendingThreePidEntity::class.java) },
+                { pendingThreePidMapper.map(it) }
+        )
+                .firstOrNull { it.threePid == params.threePid }
+                ?: throw IllegalArgumentException("unknown threepid")
+
+        try {
+            executeRequest<Unit>(eventBus) {
+                val body = FinalizeAddThreePidBody(
+                        clientSecret = pendingThreePids.clientSecret,
+                        sid = pendingThreePids.sid,
+                        auth = if (params.session != null && params.accountPassword != null) {
+                            UserPasswordAuth(
+                                    session = params.session,
+                                    user = userId,
+                                    password = params.accountPassword
+                            )
+                        } else null
+                )
+                apiCall = profileAPI.finalizeAddThreePid(body)
+            }
+        } catch (throwable: Throwable) {
+            throw throwable.toRegistrationFlowResponse()
+                    ?.let { Failure.RegistrationFlowError(it) }
+                    ?: throwable
+        }
+
+        // Delete the pending three pid
+        monarchy.awaitTransaction { realm ->
+            realm.where(PendingThreePidEntity::class.java)
+                    .equalTo(PendingThreePidEntityFields.EMAIL, params.threePid.value)
+                    .or()
+                    .equalTo(PendingThreePidEntityFields.MSISDN, params.threePid.value)
+                    .findAll()
+                    .deleteAllFromRealm()
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/PendingThreePid.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/PendingThreePid.kt
new file mode 100644
index 0000000000..d9c3a5d656
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/PendingThreePid.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.profile
+
+import org.matrix.android.sdk.api.session.identity.ThreePid
+
+internal data class PendingThreePid(
+        val threePid: ThreePid,
+        val clientSecret: String,
+        val sendAttempt: Int,
+        val sid: String
+)
+
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/PendingThreePidMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/PendingThreePidMapper.kt
new file mode 100644
index 0000000000..c9d6381bd3
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/PendingThreePidMapper.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.profile
+
+import org.matrix.android.sdk.api.session.identity.ThreePid
+import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
+import javax.inject.Inject
+
+internal class PendingThreePidMapper @Inject constructor() {
+
+    fun map(entity: PendingThreePidEntity): PendingThreePid {
+        return PendingThreePid(
+                threePid = entity.email?.let { ThreePid.Email(it) }
+                        ?: entity.msisdn?.let { ThreePid.Msisdn(it) }
+                        ?: error("Invalid data"),
+                clientSecret = entity.clientSecret,
+                sendAttempt = entity.sendAttempt,
+                sid = entity.sid
+        )
+    }
+
+    fun map(domain: PendingThreePid): PendingThreePidEntity {
+        return PendingThreePidEntity(
+                email = domain.threePid.takeIf { it is ThreePid.Email }?.value,
+                msisdn = domain.threePid.takeIf { it is ThreePid.Msisdn }?.value,
+                clientSecret = domain.clientSecret,
+                sendAttempt = domain.sendAttempt,
+                sid = domain.sid
+        )
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt
index 31e1f09bbd..125b1a47e6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt
@@ -28,7 +28,6 @@ import retrofit2.http.PUT
 import retrofit2.http.Path
 
 internal interface ProfileAPI {
-
     /**
      * Get the combined profile information for this user.
      * This API may be used to fetch the user's own profile information or other users; either locally or on remote homeservers.
@@ -71,4 +70,22 @@ internal interface ProfileAPI {
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/unbind")
     fun unbindThreePid(@Body body: UnbindThreePidBody): Call<UnbindThreePidResponse>
+
+    /**
+     * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-email-requesttoken
+     */
+    @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/email/requestToken")
+    fun addEmail(@Body body: AddEmailBody): Call<AddThreePidResponse>
+
+    /**
+     * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-add
+     */
+    @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/add")
+    fun finalizeAddThreePid(@Body body: FinalizeAddThreePidBody): Call<Unit>
+
+    /**
+     * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-delete
+     */
+    @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/delete")
+    fun deleteThreePid(@Body body: DeleteThreePidBody): Call<DeleteThreePidResponse>
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileModule.kt
index 57a86d03e0..baeaf9fd58 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileModule.kt
@@ -58,4 +58,13 @@ internal abstract class ProfileModule {
 
     @Binds
     abstract fun bindSetAvatarUrlTask(task: DefaultSetAvatarUrlTask): SetAvatarUrlTask
+
+    @Binds
+    abstract fun bindAddThreePidTask(task: DefaultAddThreePidTask): AddThreePidTask
+
+    @Binds
+    abstract fun bindFinalizeAddingThreePidTask(task: DefaultFinalizeAddingThreePidTask): FinalizeAddingThreePidTask
+
+    @Binds
+    abstract fun bindDeleteThreePidTask(task: DefaultDeleteThreePidTask): DeleteThreePidTask
 }
diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
index 591d1c0474..d0e4c938cd 100644
--- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
@@ -103,6 +103,7 @@ import im.vector.app.features.settings.ignored.VectorSettingsIgnoredUsersFragmen
 import im.vector.app.features.settings.locale.LocalePickerFragment
 import im.vector.app.features.settings.push.PushGatewaysFragment
 import im.vector.app.features.settings.push.PushRulesFragment
+import im.vector.app.features.settings.threepids.ThreePidsSettingsFragment
 import im.vector.app.features.share.IncomingShareFragment
 import im.vector.app.features.signout.soft.SoftLogoutFragment
 import im.vector.app.features.terms.ReviewTermsFragment
@@ -313,6 +314,11 @@ interface FragmentModule {
     @FragmentKey(VectorSettingsDevicesFragment::class)
     fun bindVectorSettingsDevicesFragment(fragment: VectorSettingsDevicesFragment): Fragment
 
+    @Binds
+    @IntoMap
+    @FragmentKey(ThreePidsSettingsFragment::class)
+    fun bindThreePidsSettingsFragment(fragment: ThreePidsSettingsFragment): Fragment
+
     @Binds
     @IntoMap
     @FragmentKey(PublicRoomsFragment::class)
diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt
index 051847321a..14939eaff9 100644
--- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt
+++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt
@@ -59,35 +59,39 @@ class DefaultErrorFormatter @Inject constructor(
             }
             is Failure.ServerError       -> {
                 when {
-                    throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN  -> {
+                    throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN         -> {
                         // Special case for terms and conditions
                         stringProvider.getString(R.string.error_terms_not_accepted)
                     }
-                    throwable.isInvalidPassword()                            -> {
+                    throwable.isInvalidPassword()                                   -> {
                         stringProvider.getString(R.string.auth_invalid_login_param)
                     }
-                    throwable.error.code == MatrixError.M_USER_IN_USE        -> {
+                    throwable.error.code == MatrixError.M_USER_IN_USE               -> {
                         stringProvider.getString(R.string.login_signup_error_user_in_use)
                     }
-                    throwable.error.code == MatrixError.M_BAD_JSON           -> {
+                    throwable.error.code == MatrixError.M_BAD_JSON                  -> {
                         stringProvider.getString(R.string.login_error_bad_json)
                     }
-                    throwable.error.code == MatrixError.M_NOT_JSON           -> {
+                    throwable.error.code == MatrixError.M_NOT_JSON                  -> {
                         stringProvider.getString(R.string.login_error_not_json)
                     }
-                    throwable.error.code == MatrixError.M_THREEPID_DENIED    -> {
+                    throwable.error.code == MatrixError.M_THREEPID_DENIED           -> {
                         stringProvider.getString(R.string.login_error_threepid_denied)
                     }
-                    throwable.error.code == MatrixError.M_LIMIT_EXCEEDED     -> {
+                    throwable.error.code == MatrixError.M_LIMIT_EXCEEDED            -> {
                         limitExceededError(throwable.error)
                     }
-                    throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND -> {
+                    throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND        -> {
                         stringProvider.getString(R.string.login_reset_password_error_not_found)
                     }
-                    throwable.error.code == MatrixError.M_USER_DEACTIVATED   -> {
+                    throwable.error.code == MatrixError.M_USER_DEACTIVATED          -> {
                         stringProvider.getString(R.string.auth_invalid_login_deactivated_account)
                     }
-                    else                                                     -> {
+                    throwable.error.code == MatrixError.M_THREEPID_IN_USE
+                            && throwable.error.message == "Email is already in use" -> {
+                        stringProvider.getString(R.string.account_email_already_used_error)
+                    }
+                    else                                                            -> {
                         throwable.error.message.takeIf { it.isNotEmpty() }
                                 ?: throwable.error.code.takeIf { it.isNotEmpty() }
                     }
diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericItem.kt
index 832250fab4..492df9eb00 100644
--- a/vector/src/main/java/im/vector/app/core/ui/list/GenericItem.kt
+++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericItem.kt
@@ -70,6 +70,9 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
     @EpoxyAttribute
     var buttonAction: Action? = null
 
+    @EpoxyAttribute
+    var destructiveButtonAction: Action? = null
+
     @EpoxyAttribute
     var itemClickAction: Action? = null
 
@@ -109,6 +112,11 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
             buttonAction?.perform?.run()
         }
 
+        holder.destructiveButton.setTextOrHide(destructiveButtonAction?.title)
+        holder.destructiveButton.setOnClickListener {
+            destructiveButtonAction?.perform?.run()
+        }
+
         holder.root.setOnClickListener {
             itemClickAction?.perform?.run()
         }
@@ -122,5 +130,6 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
         val accessoryImage by bind<ImageView>(R.id.item_generic_accessory_image)
         val progressBar by bind<ProgressBar>(R.id.item_generic_progress_bar)
         val actionButton by bind<Button>(R.id.item_generic_action_button)
+        val destructiveButton by bind<Button>(R.id.item_generic_destructive_action_button)
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt
index e31b81b162..c4df3a8d6e 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt
@@ -23,13 +23,11 @@ import android.content.Intent
 import android.net.Uri
 import android.os.Bundle
 import android.text.Editable
-import android.util.Patterns
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.Toast
 import androidx.appcompat.app.AlertDialog
-import androidx.core.content.ContextCompat
 import androidx.core.net.toUri
 import androidx.core.view.isVisible
 import androidx.preference.EditTextPreference
@@ -54,13 +52,11 @@ import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
 import im.vector.app.core.utils.TextUtils
 import im.vector.app.core.utils.allGranted
 import im.vector.app.core.utils.checkPermissions
-import im.vector.app.core.utils.copyToClipboard
 import im.vector.app.core.utils.getSizeOfFiles
 import im.vector.app.core.utils.toast
 import im.vector.app.features.MainActivity
 import im.vector.app.features.MainActivityArgs
 import im.vector.app.features.media.createUCropWithDefaultSettings
-import im.vector.app.features.themes.ThemeUtils
 import im.vector.app.features.workers.signout.SignOutUiWorker
 import im.vector.lib.multipicker.MultiPicker
 import im.vector.lib.multipicker.entity.MultiPickerImageType
@@ -187,44 +183,6 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
             mPasswordPreference.isVisible = false
         }
 
-        // Add Email
-        findPreference<EditTextPreference>(ADD_EMAIL_PREFERENCE_KEY)!!.let {
-            // It does not work on XML, do it here
-            it.icon = activity?.let {
-                ThemeUtils.tintDrawable(it,
-                        ContextCompat.getDrawable(it, R.drawable.ic_material_add)!!, R.attr.colorAccent)
-            }
-
-            // Unfortunately, this is not supported in lib v7
-            // it.editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
-            it.setOnPreferenceClickListener {
-                notImplemented()
-                true
-            }
-
-            it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
-                notImplemented()
-                // addEmail((newValue as String).trim())
-                false
-            }
-        }
-
-        // Add phone number
-        findPreference<VectorPreference>(ADD_PHONE_NUMBER_PREFERENCE_KEY)!!.let {
-            // It does not work on XML, do it here
-            it.icon = activity?.let {
-                ThemeUtils.tintDrawable(it,
-                        ContextCompat.getDrawable(it, R.drawable.ic_material_add)!!, R.attr.colorAccent)
-            }
-
-            it.setOnPreferenceClickListener {
-                notImplemented()
-                // TODO val intent = PhoneNumberAdditionActivity.getIntent(activity, session.credentials.userId)
-                // startActivityForResult(intent, REQUEST_NEW_PHONE_NUMBER)
-                true
-            }
-        }
-
         // Advanced settings
 
         // user account
@@ -235,8 +193,6 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
         findPreference<VectorPreference>(VectorPreferences.SETTINGS_HOME_SERVER_PREFERENCE_KEY)!!
                 .summary = session.sessionParams.homeServerUrl
 
-        refreshEmailsList()
-        refreshPhoneNumbersList()
         // Contacts
         setContactsPreferences()
 
@@ -533,295 +489,6 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
      * Refresh phone number list
      */
     private fun refreshPhoneNumbersList() {
-        /* TODO
-        val currentPhoneNumber3PID = ArrayList(session.myUser.getlinkedPhoneNumbers())
-
-        val phoneNumberList = ArrayList<String>()
-        for (identifier in currentPhoneNumber3PID) {
-            phoneNumberList.add(identifier.address)
-        }
-
-        // check first if there is an update
-        var isNewList = true
-        if (phoneNumberList.size == mDisplayedPhoneNumber.size) {
-            isNewList = !mDisplayedPhoneNumber.containsAll(phoneNumberList)
-        }
-
-        if (isNewList) {
-            // remove the displayed one
-            run {
-                var index = 0
-                while (true) {
-                    val preference = mUserSettingsCategory.findPreference(PHONE_NUMBER_PREFERENCE_KEY_BASE + index)
-
-                    if (null != preference) {
-                        mUserSettingsCategory.removePreference(preference)
-                    } else {
-                        break
-                    }
-                    index++
-                }
-            }
-
-            // add new phone number list
-            mDisplayedPhoneNumber = phoneNumberList
-
-            val addPhoneBtn = mUserSettingsCategory.findPreference(ADD_PHONE_NUMBER_PREFERENCE_KEY)
-                    ?: return
-
-            var order = addPhoneBtn.order
-
-            for ((index, phoneNumber3PID) in currentPhoneNumber3PID.withIndex()) {
-                val preference = VectorPreference(activity!!)
-
-                preference.title = getString(R.string.settings_phone_number)
-                var phoneNumberFormatted = phoneNumber3PID.address
-                try {
-                    // Attempt to format phone number
-                    val phoneNumber = PhoneNumberUtil.getInstance().parse("+$phoneNumberFormatted", null)
-                    phoneNumberFormatted = PhoneNumberUtil.getInstance().format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL)
-                } catch (e: NumberParseException) {
-                    // Do nothing, we will display raw version
-                }
-
-                preference.summary = phoneNumberFormatted
-                preference.key = PHONE_NUMBER_PREFERENCE_KEY_BASE + index
-                preference.order = order
-
-                preference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
-                    displayDelete3PIDConfirmationDialog(phoneNumber3PID, preference.summary)
-                    true
-                }
-
-                preference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener {
-                    override fun onPreferenceLongClick(preference: Preference): Boolean {
-                        activity?.let { copyToClipboard(it, phoneNumber3PID.address) }
-                        return true
-                    }
-                }
-
-                order++
-                mUserSettingsCategory.addPreference(preference)
-            }
-
-            addPhoneBtn.order = order
-        }    */
-    }
-
-// ==============================================================================================================
-// Email management
-// ==============================================================================================================
-
-    /**
-     * Refresh the emails list
-     */
-    private fun refreshEmailsList() {
-        val currentEmail3PID = emptyList<String>() // TODO ArrayList(session.myUser.getlinkedEmails())
-
-        val newEmailsList = ArrayList<String>()
-        for (identifier in currentEmail3PID) {
-            // TODO newEmailsList.add(identifier.address)
-        }
-
-        // check first if there is an update
-        var isNewList = true
-        if (newEmailsList.size == mDisplayedEmails.size) {
-            isNewList = !mDisplayedEmails.containsAll(newEmailsList)
-        }
-
-        if (isNewList) {
-            // remove the displayed one
-            run {
-                var index = 0
-                while (true) {
-                    val preference = mUserSettingsCategory.findPreference<VectorPreference>(EMAIL_PREFERENCE_KEY_BASE + index)
-
-                    if (null != preference) {
-                        mUserSettingsCategory.removePreference(preference)
-                    } else {
-                        break
-                    }
-                    index++
-                }
-            }
-
-            // add new emails list
-            mDisplayedEmails = newEmailsList
-
-            val addEmailBtn = mUserSettingsCategory.findPreference<VectorPreference>(ADD_EMAIL_PREFERENCE_KEY) ?: return
-
-            var order = addEmailBtn.order
-
-            for ((index, email3PID) in currentEmail3PID.withIndex()) {
-                val preference = VectorPreference(requireActivity())
-
-                preference.title = getString(R.string.settings_email_address)
-                preference.summary = "TODO" // email3PID.address
-                preference.key = EMAIL_PREFERENCE_KEY_BASE + index
-                preference.order = order
-
-                preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { pref ->
-                    displayDelete3PIDConfirmationDialog(/* TODO email3PID, */ pref.summary)
-                    true
-                }
-
-                preference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener {
-                    override fun onPreferenceLongClick(preference: Preference): Boolean {
-                        activity?.let { copyToClipboard(it, "TODO") } // email3PID.address) }
-                        return true
-                    }
-                }
-
-                mUserSettingsCategory.addPreference(preference)
-
-                order++
-            }
-
-            addEmailBtn.order = order
-        }
-    }
-
-    /**
-     * Attempt to add a new email to the account
-     *
-     * @param email the email to add.
-     */
-    private fun addEmail(email: String) {
-        // check first if the email syntax is valid
-        // if email is null , then also its invalid email
-        if (email.isBlank() || !Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
-            activity?.toast(R.string.auth_invalid_email)
-            return
-        }
-
-        // check first if the email syntax is valid
-        if (mDisplayedEmails.indexOf(email) >= 0) {
-            activity?.toast(R.string.auth_email_already_defined)
-            return
-        }
-
-        notImplemented()
-        /* TODO
-        val pid = ThreePid(email, ThreePid.MEDIUM_EMAIL)
-
-        displayLoadingView()
-
-        session.myUser.requestEmailValidationToken(pid, object : MatrixCallback<Unit> {
-            override fun onSuccess(info: Void?) {
-                activity?.runOnUiThread { showEmailValidationDialog(pid) }
-            }
-
-            override fun onNetworkError(e: Exception) {
-                onCommonDone(e.localizedMessage)
-            }
-
-            override fun onMatrixError(e: MatrixError) {
-                if (TextUtils.equals(MatrixError.THREEPID_IN_USE, e.errcode)) {
-                    onCommonDone(getString(R.string.account_email_already_used_error))
-                } else {
-                    onCommonDone(e.localizedMessage)
-                }
-            }
-
-            override fun onUnexpectedError(e: Exception) {
-                onCommonDone(e.localizedMessage)
-            }
-        })
-        */
-    }
-
-    /**
-     * Show an email validation dialog to warn the user tho valid his email link.
-     *
-     * @param pid the used pid.
-     */
-/* TODO
-private fun showEmailValidationDialog(pid: ThreePid) {
-    activity?.let {
-        AlertDialog.Builder(it)
-                .setTitle(R.string.account_email_validation_title)
-                .setMessage(R.string.account_email_validation_message)
-                .setPositiveButton(R.string._continue) { _, _ ->
-                    session.myUser.add3Pid(pid, true, object : MatrixCallback<Unit> {
-                        override fun onSuccess(info: Void?) {
-                            it.runOnUiThread {
-                                hideLoadingView()
-                                refreshEmailsList()
-                            }
-                        }
-
-                        override fun onNetworkError(e: Exception) {
-                            onCommonDone(e.localizedMessage)
-                        }
-
-                        override fun onMatrixError(e: MatrixError) {
-                            if (TextUtils.equals(e.errcode, MatrixError.THREEPID_AUTH_FAILED)) {
-                                it.runOnUiThread {
-                                    hideLoadingView()
-                                    it.toast(R.string.account_email_validation_error)
-                                }
-                            } else {
-                                onCommonDone(e.localizedMessage)
-                            }
-                        }
-
-                        override fun onUnexpectedError(e: Exception) {
-                            onCommonDone(e.localizedMessage)
-                        }
-                    })
-                }
-                .setNegativeButton(R.string.cancel) { _, _ ->
-                    hideLoadingView()
-                }
-                .show()
-    }
-}    */
-
-    /**
-     * Display a dialog which asks confirmation for the deletion of a 3pid
-     *
-     * @param pid               the 3pid to delete
-     * @param preferenceSummary the displayed 3pid
-     */
-    private fun displayDelete3PIDConfirmationDialog(/* TODO pid: ThirdPartyIdentifier,*/ preferenceSummary: CharSequence) {
-        val mediumFriendlyName = "TODO" // ThreePid.getMediumFriendlyName(pid.medium, activity).toLowerCase(VectorLocale.applicationLocale)
-        val dialogMessage = getString(R.string.settings_delete_threepid_confirmation, mediumFriendlyName, preferenceSummary)
-
-        activity?.let {
-            AlertDialog.Builder(it)
-                    .setTitle(R.string.dialog_title_confirmation)
-                    .setMessage(dialogMessage)
-                    .setPositiveButton(R.string.remove) { _, _ ->
-                        notImplemented()
-                        /* TODO
-                        displayLoadingView()
-
-                        session.myUser.delete3Pid(pid, object : MatrixCallback<Unit> {
-                            override fun onSuccess(info: Void?) {
-                                when (pid.medium) {
-                                    ThreePid.MEDIUM_EMAIL -> refreshEmailsList()
-                                    ThreePid.MEDIUM_MSISDN -> refreshPhoneNumbersList()
-                                }
-                                onCommonDone(null)
-                            }
-
-                            override fun onNetworkError(e: Exception) {
-                                onCommonDone(e.localizedMessage)
-                            }
-
-                            override fun onMatrixError(e: MatrixError) {
-                                onCommonDone(e.localizedMessage)
-                            }
-
-                            override fun onUnexpectedError(e: Exception) {
-                                onCommonDone(e.localizedMessage)
-                            }
-                        })
-                        */
-                    }
-                    .setNegativeButton(R.string.cancel, null)
-                    .show()
-        }
     }
 
     /**
@@ -985,12 +652,6 @@ private fun showEmailValidationDialog(pid: ThreePid) {
     }
 
     companion object {
-        private const val ADD_EMAIL_PREFERENCE_KEY = "ADD_EMAIL_PREFERENCE_KEY"
-        private const val ADD_PHONE_NUMBER_PREFERENCE_KEY = "ADD_PHONE_NUMBER_PREFERENCE_KEY"
-
-        private const val EMAIL_PREFERENCE_KEY_BASE = "EMAIL_PREFERENCE_KEY_BASE"
-        private const val PHONE_NUMBER_PREFERENCE_KEY_BASE = "PHONE_NUMBER_PREFERENCE_KEY_BASE"
-
         private const val REQUEST_NEW_PHONE_NUMBER = 456
         private const val REQUEST_PHONEBOOK_COUNTRY = 789
     }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt
index 237c8c218d..37a0d392a1 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt
@@ -28,6 +28,11 @@ import com.airbnb.mvrx.Uninitialized
 import com.airbnb.mvrx.ViewModelContext
 import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
+import im.vector.app.core.platform.VectorViewModel
+import io.reactivex.Observable
+import io.reactivex.functions.BiFunction
+import io.reactivex.subjects.PublishSubject
+import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.NoOpMatrixCallback
 import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
@@ -41,11 +46,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
 import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
 import org.matrix.android.sdk.internal.util.awaitCallback
-import im.vector.app.core.platform.VectorViewModel
-import io.reactivex.Observable
-import io.reactivex.functions.BiFunction
-import io.reactivex.subjects.PublishSubject
-import kotlinx.coroutines.launch
 import org.matrix.android.sdk.rx.rx
 import timber.log.Timber
 import java.util.concurrent.TimeUnit
@@ -309,7 +309,7 @@ class DevicesViewModel @AssistedInject constructor(
                 }
 
                 if (!isPasswordRequestFound) {
-                    // LoginFlowTypes.PASSWORD not supported, and this is the only one RiotX supports so far...
+                    // LoginFlowTypes.PASSWORD not supported, and this is the only one Element supports so far...
                     setState {
                         copy(
                                 request = Fail(failure)
diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsAction.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsAction.kt
new file mode 100644
index 0000000000..07ce6b6744
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsAction.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.threepids
+
+import im.vector.app.core.platform.VectorViewModelAction
+import org.matrix.android.sdk.api.session.identity.ThreePid
+
+sealed class ThreePidsSettingsAction : VectorViewModelAction {
+    data class AddThreePid(val threePid: ThreePid) : ThreePidsSettingsAction()
+    data class ContinueThreePid(val threePid: ThreePid) : ThreePidsSettingsAction()
+    data class AccountPassword(val password: String) : ThreePidsSettingsAction()
+    data class DeleteThreePid(val threePid: ThreePid) : ThreePidsSettingsAction()
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt
new file mode 100644
index 0000000000..dc59dd4ad2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.threepids
+
+import android.view.View
+import com.airbnb.epoxy.TypedEpoxyController
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import im.vector.app.R
+import im.vector.app.core.epoxy.loadingItem
+import im.vector.app.core.resources.ColorProvider
+import im.vector.app.core.resources.StringProvider
+import im.vector.app.core.ui.list.GenericItem
+import im.vector.app.core.ui.list.genericButtonItem
+import im.vector.app.core.ui.list.genericFooterItem
+import im.vector.app.core.ui.list.genericItem
+import im.vector.app.features.discovery.settingsSectionTitleItem
+import org.matrix.android.sdk.api.session.identity.ThreePid
+import javax.inject.Inject
+
+class ThreePidsSettingsController @Inject constructor(
+        private val stringProvider: StringProvider,
+        private val colorProvider: ColorProvider
+) : TypedEpoxyController<ThreePidsSettingsViewState>() {
+
+    interface InteractionListener {
+        fun addEmail()
+        fun addMsisdn()
+        fun continueThreePid(threePid: ThreePid)
+        fun deleteThreePid(threePid: ThreePid)
+    }
+
+    var interactionListener: InteractionListener? = null
+
+    override fun buildModels(data: ThreePidsSettingsViewState?) {
+        if (data == null) return
+        when (data.threePids) {
+            is Loading -> {
+                loadingItem {
+                    id("loading")
+                    loadingText(stringProvider.getString(R.string.loading))
+                }
+            }
+            is Fail -> {
+                genericFooterItem {
+                    id("fail")
+                    text(data.threePids.error.localizedMessage)
+                }
+            }
+            is Success -> {
+                val dataList = data.threePids.invoke()
+                buildThreePids(dataList, data.pendingThreePids)
+            }
+        }
+    }
+
+    private fun buildThreePids(list: List<ThreePid>, pendingThreePids: Async<List<ThreePid>>) {
+        val splited = list.groupBy { it is ThreePid.Email }
+        val emails = splited[true].orEmpty()
+        val msisdn = splited[false].orEmpty()
+
+        settingsSectionTitleItem {
+            id("email")
+            title(stringProvider.getString(R.string.settings_emails))
+        }
+
+        emails.forEach { buildThreePid("email_", it) }
+
+        // Pending threePids
+        pendingThreePids.invoke()
+                ?.filterIsInstance(ThreePid.Email::class.java)
+                ?.forEach { buildPendingThreePid("email_", it) }
+
+        genericButtonItem {
+            id("addEmail")
+            text(stringProvider.getString(R.string.settings_add_email_address))
+            textColor(colorProvider.getColor(R.color.riotx_accent))
+            buttonClickAction(View.OnClickListener { interactionListener?.addEmail() })
+        }
+
+        settingsSectionTitleItem {
+            id("msisdn")
+            title(stringProvider.getString(R.string.settings_phone_numbers))
+        }
+
+        msisdn.forEach { buildThreePid("msisdn_", it) }
+
+        // Pending threePids
+        pendingThreePids.invoke()
+                ?.filterIsInstance(ThreePid.Msisdn::class.java)
+                ?.forEach { buildPendingThreePid("msisdn_", it) }
+
+        genericButtonItem {
+            id("addMsisdn")
+            text(stringProvider.getString(R.string.settings_add_phone_number))
+            textColor(colorProvider.getColor(R.color.riotx_accent))
+            buttonClickAction(View.OnClickListener { interactionListener?.addMsisdn() })
+        }
+    }
+
+    private fun buildThreePid(idPrefix: String, threePid: ThreePid) {
+        genericItem {
+            id(idPrefix + threePid.value)
+            title(threePid.value)
+            destructiveButtonAction(
+                    GenericItem.Action(stringProvider.getString(R.string.remove))
+                            .apply {
+                                perform = Runnable { interactionListener?.deleteThreePid(threePid) }
+                            }
+            )
+        }
+    }
+
+    private fun buildPendingThreePid(idPrefix: String, threePid: ThreePid) {
+        genericItem {
+            id(idPrefix + threePid.value)
+            title(threePid.value)
+            if (threePid is ThreePid.Email) {
+                description(stringProvider.getString(R.string.account_email_validation_message))
+            }
+            buttonAction(
+                    GenericItem.Action(stringProvider.getString(R.string._continue))
+                            .apply {
+                                perform = Runnable { interactionListener?.continueThreePid(threePid) }
+                            }
+            )
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt
new file mode 100644
index 0000000000..875237bdc4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.threepids
+
+import android.content.DialogInterface
+import android.os.Bundle
+import android.text.InputType
+import android.view.View
+import android.widget.EditText
+import androidx.appcompat.app.AlertDialog
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
+import im.vector.app.R
+import im.vector.app.core.dialogs.PromptPasswordDialog
+import im.vector.app.core.dialogs.withColoredButton
+import im.vector.app.core.extensions.cleanup
+import im.vector.app.core.extensions.configureWith
+import im.vector.app.core.extensions.exhaustive
+import im.vector.app.core.extensions.isEmail
+import im.vector.app.core.platform.VectorBaseActivity
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.core.utils.toast
+import kotlinx.android.synthetic.main.fragment_generic_recycler.*
+import org.matrix.android.sdk.api.session.identity.ThreePid
+import javax.inject.Inject
+
+class ThreePidsSettingsFragment @Inject constructor(
+        private val viewModelFactory: ThreePidsSettingsViewModel.Factory,
+        private val epoxyController: ThreePidsSettingsController
+) :
+        VectorBaseFragment(),
+        ThreePidsSettingsViewModel.Factory by viewModelFactory,
+        ThreePidsSettingsController.InteractionListener {
+
+    private val viewModel: ThreePidsSettingsViewModel by fragmentViewModel()
+
+    override fun getLayoutResId() = R.layout.fragment_generic_recycler
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        recyclerView.configureWith(epoxyController)
+        epoxyController.interactionListener = this
+
+        viewModel.observeViewEvents {
+            when (it) {
+                is ThreePidsSettingsViewEvents.Failure -> displayErrorDialog(it.throwable)
+                ThreePidsSettingsViewEvents.RequestPassword -> askUserPassword()
+            }.exhaustive
+        }
+    }
+
+    private fun askUserPassword() {
+        PromptPasswordDialog().show(requireActivity()) { password ->
+            viewModel.handle(ThreePidsSettingsAction.AccountPassword(password))
+        }
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        recyclerView.cleanup()
+        epoxyController.interactionListener = null
+    }
+
+    override fun onResume() {
+        super.onResume()
+        (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_emails_and_phone_numbers_title)
+    }
+
+    override fun invalidate() = withState(viewModel) { state ->
+        if (state.isLoading) {
+            showLoadingDialog()
+        } else {
+            dismissLoadingDialog()
+        }
+        epoxyController.setData(state)
+    }
+
+    override fun addEmail() {
+        val inflater = requireActivity().layoutInflater
+        val layout = inflater.inflate(R.layout.dialog_base_edit_text, null)
+
+        val input = layout.findViewById<EditText>(R.id.editText)
+        input.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+
+        AlertDialog.Builder(requireActivity())
+                .setTitle(R.string.settings_add_email_address)
+                .setView(layout)
+                .setPositiveButton(R.string.ok) { _, _ ->
+                    val email = input.text.toString()
+                    doAddEmail(email)
+                }
+                .setNegativeButton(R.string.cancel, null)
+                .show()
+    }
+
+    private fun doAddEmail(email: String) {
+        // Check that email is valid
+        if (!email.isEmail()) {
+            requireActivity().toast(R.string.auth_invalid_email)
+            return
+        }
+
+        viewModel.handle(ThreePidsSettingsAction.AddThreePid(ThreePid.Email(email)))
+    }
+
+    override fun addMsisdn() {
+        TODO("Not yet implemented")
+    }
+
+    override fun continueThreePid(threePid: ThreePid) {
+        viewModel.handle(ThreePidsSettingsAction.ContinueThreePid(threePid))
+    }
+
+    override fun deleteThreePid(threePid: ThreePid) {
+        AlertDialog.Builder(requireActivity())
+                .setMessage(getString(R.string.settings_remove_three_pid_confirmation_content, threePid.value))
+                .setPositiveButton(R.string.remove) { _, _ ->
+                    viewModel.handle(ThreePidsSettingsAction.DeleteThreePid(threePid))
+                }
+                .setNegativeButton(R.string.cancel, null)
+                .show()
+                .withColoredButton(DialogInterface.BUTTON_POSITIVE)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewEvents.kt
new file mode 100644
index 0000000000..1ac2d10458
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewEvents.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.threepids
+
+import im.vector.app.core.platform.VectorViewEvents
+
+sealed class ThreePidsSettingsViewEvents : VectorViewEvents {
+    data class Failure(val throwable: Throwable) : ThreePidsSettingsViewEvents()
+    object RequestPassword : ThreePidsSettingsViewEvents()
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt
new file mode 100644
index 0000000000..85eb855569
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.threepids
+
+import androidx.lifecycle.viewModelScope
+import com.airbnb.mvrx.ActivityViewModelContext
+import com.airbnb.mvrx.FragmentViewModelContext
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.ViewModelContext
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import im.vector.app.core.extensions.exhaustive
+import im.vector.app.core.platform.VectorViewModel
+import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
+import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.identity.ThreePid
+import org.matrix.android.sdk.rx.rx
+
+class ThreePidsSettingsViewModel @AssistedInject constructor(
+        @Assisted initialState: ThreePidsSettingsViewState,
+        private val session: Session
+) : VectorViewModel<ThreePidsSettingsViewState, ThreePidsSettingsAction, ThreePidsSettingsViewEvents>(initialState) {
+
+    // UIA session
+    private var pendingThreePid: ThreePid? = null
+    private var pendingSession: String? = null
+
+    private val loadingCallback: MatrixCallback<Unit> = object : MatrixCallback<Unit> {
+        override fun onFailure(failure: Throwable) {
+            isLoading(false)
+
+            if (failure is Failure.RegistrationFlowError) {
+                var isPasswordRequestFound = false
+
+                // We only support LoginFlowTypes.PASSWORD
+                // Check if we can provide the user password
+                failure.registrationFlowResponse.flows?.forEach { interactiveAuthenticationFlow ->
+                    isPasswordRequestFound = isPasswordRequestFound || interactiveAuthenticationFlow.stages?.any { it == LoginFlowTypes.PASSWORD } == true
+                }
+
+                if (isPasswordRequestFound) {
+                    pendingSession = failure.registrationFlowResponse.session
+                    _viewEvents.post(ThreePidsSettingsViewEvents.RequestPassword)
+                } else {
+                    // LoginFlowTypes.PASSWORD not supported, and this is the only one Element supports so far...
+                    _viewEvents.post(ThreePidsSettingsViewEvents.Failure(failure))
+                }
+            } else {
+                _viewEvents.post(ThreePidsSettingsViewEvents.Failure(failure))
+            }
+        }
+
+        override fun onSuccess(data: Unit) {
+            pendingThreePid = null
+            pendingSession = null
+            isLoading(false)
+        }
+    }
+
+    private fun isLoading(isLoading: Boolean) {
+        setState {
+            copy(
+                    isLoading = isLoading
+            )
+        }
+    }
+
+    @AssistedInject.Factory
+    interface Factory {
+        fun create(initialState: ThreePidsSettingsViewState): ThreePidsSettingsViewModel
+    }
+
+    companion object : MvRxViewModelFactory<ThreePidsSettingsViewModel, ThreePidsSettingsViewState> {
+
+        @JvmStatic
+        override fun create(viewModelContext: ViewModelContext, state: ThreePidsSettingsViewState): ThreePidsSettingsViewModel? {
+            val factory = when (viewModelContext) {
+                is FragmentViewModelContext -> viewModelContext.fragment as? Factory
+                is ActivityViewModelContext -> viewModelContext.activity as? Factory
+            }
+            return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
+        }
+    }
+
+    init {
+        observeThreePids()
+        observePendingThreePids()
+    }
+
+    private fun observeThreePids() {
+        session.rx()
+                .liveThreePIds(true)
+                .execute {
+                    copy(
+                            threePids = it
+                    )
+                }
+    }
+
+    private fun observePendingThreePids() {
+        session.rx()
+                .livePendingThreePIds()
+                .execute {
+                    copy(
+                            pendingThreePids = it
+                    )
+                }
+    }
+
+    override fun handle(action: ThreePidsSettingsAction) {
+        when (action) {
+            is ThreePidsSettingsAction.AddThreePid      -> handleAddThreePid(action)
+            is ThreePidsSettingsAction.ContinueThreePid -> handleContinueThreePid(action)
+            is ThreePidsSettingsAction.AccountPassword  -> handleAccountPassword(action)
+            is ThreePidsSettingsAction.DeleteThreePid   -> handleDeleteThreePid(action)
+        }.exhaustive
+    }
+
+    private fun handleAddThreePid(action: ThreePidsSettingsAction.AddThreePid) {
+        isLoading(true)
+        viewModelScope.launch {
+            session.addThreePid(action.threePid, loadingCallback)
+        }
+    }
+
+    private fun handleContinueThreePid(action: ThreePidsSettingsAction.ContinueThreePid) {
+        isLoading(true)
+        pendingThreePid = action.threePid
+        viewModelScope.launch {
+            session.finalizeAddingThreePid(action.threePid, null, null, loadingCallback)
+        }
+    }
+
+    private fun handleAccountPassword(action: ThreePidsSettingsAction.AccountPassword) {
+        val safeSession = pendingSession ?: return Unit
+                .also { _viewEvents.post(ThreePidsSettingsViewEvents.Failure(IllegalStateException("No pending session"))) }
+        val safeThreePid = pendingThreePid ?: return Unit
+                .also { _viewEvents.post(ThreePidsSettingsViewEvents.Failure(IllegalStateException("No pending threePid"))) }
+        isLoading(true)
+        viewModelScope.launch {
+            session.finalizeAddingThreePid(safeThreePid, safeSession, action.password, loadingCallback)
+        }
+    }
+
+    private fun handleDeleteThreePid(action: ThreePidsSettingsAction.DeleteThreePid) {
+        isLoading(true)
+        viewModelScope.launch {
+            session.deleteThreePid(action.threePid, loadingCallback)
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewState.kt
new file mode 100644
index 0000000000..10ac51f229
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewState.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.threepids
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MvRxState
+import com.airbnb.mvrx.Uninitialized
+import org.matrix.android.sdk.api.session.identity.ThreePid
+
+data class ThreePidsSettingsViewState(
+        val isLoading: Boolean = false,
+        val threePids: Async<List<ThreePid>> = Uninitialized,
+        val pendingThreePids: Async<List<ThreePid>> = Uninitialized
+) : MvRxState
diff --git a/vector/src/main/res/layout/item_generic_list.xml b/vector/src/main/res/layout/item_generic_list.xml
index 4b206c352d..f89413f15f 100644
--- a/vector/src/main/res/layout/item_generic_list.xml
+++ b/vector/src/main/res/layout/item_generic_list.xml
@@ -91,7 +91,7 @@
         app:layout_constraintTop_toTopOf="@+id/item_generic_title_text"
         tools:visibility="visible" />
 
-    <!-- Set a maw width because the text can be long -->
+    <!-- Set a max width because the text can be long -->
     <com.google.android.material.button.MaterialButton
         android:id="@+id/item_generic_action_button"
         style="@style/VectorButtonStyle"
@@ -102,10 +102,26 @@
         android:layout_marginBottom="16dp"
         android:maxWidth="@dimen/button_max_width"
         android:visibility="gone"
-        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintBottom_toTopOf="@+id/item_generic_destructive_action_button"
         app:layout_constraintEnd_toStartOf="@+id/item_generic_barrier"
         app:layout_constraintTop_toBottomOf="@+id/item_generic_description_text"
         tools:text="@string/settings_troubleshoot_test_device_settings_quickfix"
         tools:visibility="visible" />
 
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/item_generic_destructive_action_button"
+        style="@style/VectorButtonStyleDestructive"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginBottom="16dp"
+        android:maxWidth="@dimen/button_max_width"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/item_generic_barrier"
+        app:layout_constraintTop_toBottomOf="@+id/item_generic_action_button"
+        tools:text="@string/delete"
+        tools:visibility="visible" />
+
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 7506a4d502..c22dfc6be8 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -682,6 +682,9 @@
     <string name="settings_add_3pid_flow_not_supported">You can\'t do this from Element mobile</string>
     <string name="settings_add_3pid_authentication_needed">Authentication is required</string>
 
+    <string name="settings_emails">Email addresses</string>
+    <string name="settings_phone_numbers">Phone numbers</string>
+    <string name="settings_remove_three_pid_confirmation_content">Remove %s?</string>
 
     <string name="settings_notification_advanced">Advanced Notification Settings</string>
     <string name="settings_notification_by_event">Notification importance by event</string>
@@ -927,6 +930,9 @@
     <string name="settings_unignore_user">Show all messages from %s?\n\nNote that this action will restart the app and it may take some time.</string>
     <string name="passwords_do_not_match">Passwords do not match</string>
 
+    <string name="settings_emails_and_phone_numbers_title">Emails and phone numbers</string>
+    <string name="settings_emails_and_phone_numbers_summary">Manage emails and phone numbers linked to your Matrix account</string>
+
     <string name="settings_delete_notification_targets_confirmation">Are you sure you want to remove this notification target?</string>
 
     <string name="settings_delete_threepid_confirmation">Are you sure you want to remove the %1$s %2$s?</string>
diff --git a/vector/src/main/res/xml/vector_settings_general.xml b/vector/src/main/res/xml/vector_settings_general.xml
index 042ea5c77c..5a9d016842 100644
--- a/vector/src/main/res/xml/vector_settings_general.xml
+++ b/vector/src/main/res/xml/vector_settings_general.xml
@@ -22,33 +22,11 @@
             android:summary="@string/change_password_summary"
             android:title="@string/settings_password" />
 
-        <!-- Email will be added here -->
-
-        <!-- Note: inputType does not work, it is set also in code, as well as iconTint -->
-        <im.vector.app.core.preference.VectorEditTextPreference
-            android:icon="@drawable/ic_material_add"
-            android:inputType="textEmailAddress"
-            android:key="ADD_EMAIL_PREFERENCE_KEY"
-            android:order="100"
-            android:title="@string/settings_add_email_address"
-            app:iconTint="@color/riotx_accent" />
-
-        <!-- Phone will be added here -->
-
-        <!-- Note: iconTint does not work, it is also done in code -->
         <im.vector.app.core.preference.VectorPreference
-            android:icon="@drawable/ic_material_add"
-            android:key="ADD_PHONE_NUMBER_PREFERENCE_KEY"
-            android:order="200"
-            android:title="@string/settings_add_phone_number"
-            app:iconTint="@color/riotx_accent" />
-
-        <im.vector.app.core.preference.VectorPreference
-            android:order="1000"
-            android:persistent="false"
-            android:summary="@string/settings_discovery_manage"
-            android:title="@string/settings_discovery_category"
-            app:fragment="im.vector.app.features.discovery.DiscoverySettingsFragment" />
+            android:key="SETTINGS_EMAILS_AND_PHONE_NUMBERS_PREFERENCE_KEY"
+            android:summary="@string/settings_emails_and_phone_numbers_summary"
+            android:title="@string/settings_emails_and_phone_numbers_title"
+            app:fragment="im.vector.app.features.settings.threepids.ThreePidsSettingsFragment" />
 
     </im.vector.app.core.preference.VectorPreferenceCategory>