diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt
index f011d317cd..bb74b5afa5 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt
@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.room.members
 
 import androidx.lifecycle.LiveData
 import im.vector.matrix.android.api.MatrixCallback
+import im.vector.matrix.android.api.session.identity.ThreePid
 import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
 import im.vector.matrix.android.api.util.Cancelable
 
@@ -63,6 +64,12 @@ interface MembershipService {
                reason: String? = null,
                callback: MatrixCallback<Unit>): Cancelable
 
+    /**
+     * Invite a user with email or phone number in the room
+     */
+    fun invite3pid(threePid: ThreePid,
+                   callback: MatrixCallback<Unit>): Cancelable
+
     /**
      * Ban a user from the room
      */
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt
index 3f10bf791c..13c97599f7 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt
@@ -62,6 +62,7 @@ import javax.net.ssl.HttpsURLConnection
 @SessionScope
 internal class DefaultIdentityService @Inject constructor(
         private val identityStore: IdentityStore,
+        private val ensureIdentityTokenTask: EnsureIdentityTokenTask,
         private val getOpenIdTokenTask: GetOpenIdTokenTask,
         private val identityBulkLookupTask: IdentityBulkLookupTask,
         private val identityRegisterTask: IdentityRegisterTask,
@@ -278,7 +279,7 @@ internal class DefaultIdentityService @Inject constructor(
     }
 
     private suspend fun lookUpInternal(canRetry: Boolean, threePids: List<ThreePid>): List<FoundThreePid> {
-        ensureToken()
+        ensureIdentityTokenTask.execute(Unit)
 
         return try {
             identityBulkLookupTask.execute(IdentityBulkLookupTask.Params(threePids))
@@ -295,17 +296,6 @@ internal class DefaultIdentityService @Inject constructor(
         }
     }
 
-    private suspend fun ensureToken() {
-        val identityData = identityStore.getIdentityData() ?: throw IdentityServiceError.NoIdentityServerConfigured
-        val url = identityData.identityServerUrl ?: throw IdentityServiceError.NoIdentityServerConfigured
-
-        if (identityData.token == null) {
-            // Try to get a token
-            val token = getNewIdentityServerToken(url)
-            identityStore.setToken(token)
-        }
-    }
-
     private suspend fun getNewIdentityServerToken(url: String): String {
         val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/EnsureIdentityToken.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/EnsureIdentityToken.kt
new file mode 100644
index 0000000000..e727cd69bc
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/EnsureIdentityToken.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.matrix.android.internal.session.identity
+
+import dagger.Lazy
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
+import im.vector.matrix.android.internal.di.UnauthenticatedWithCertificate
+import im.vector.matrix.android.internal.network.RetrofitFactory
+import im.vector.matrix.android.internal.session.identity.data.IdentityStore
+import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
+import im.vector.matrix.android.internal.task.Task
+import okhttp3.OkHttpClient
+import javax.inject.Inject
+
+internal interface EnsureIdentityTokenTask : Task<Unit, Unit>
+
+internal class DefaultEnsureIdentityTokenTask @Inject constructor(
+        private val identityStore: IdentityStore,
+        private val retrofitFactory: RetrofitFactory,
+        @UnauthenticatedWithCertificate
+        private val unauthenticatedOkHttpClient: Lazy<OkHttpClient>,
+        private val getOpenIdTokenTask: GetOpenIdTokenTask,
+        private val identityRegisterTask: IdentityRegisterTask
+) : EnsureIdentityTokenTask {
+
+    override suspend fun execute(params: Unit) {
+        val identityData = identityStore.getIdentityData() ?: throw IdentityServiceError.NoIdentityServerConfigured
+        val url = identityData.identityServerUrl ?: throw IdentityServiceError.NoIdentityServerConfigured
+
+        if (identityData.token == null) {
+            // Try to get a token
+            val token = getNewIdentityServerToken(url)
+            identityStore.setToken(token)
+        }
+    }
+
+    private suspend fun getNewIdentityServerToken(url: String): String {
+        val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
+
+        val openIdToken = getOpenIdTokenTask.execute(Unit)
+        val token = identityRegisterTask.execute(IdentityRegisterTask.Params(api, openIdToken))
+
+        return token.token
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt
index 9f902f79f1..79160b8c59 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt
@@ -78,6 +78,9 @@ internal abstract class IdentityModule {
     @Binds
     abstract fun bindIdentityStore(store: RealmIdentityStore): IdentityStore
 
+    @Binds
+    abstract fun bindEnsureIdentityTokenTask(task: DefaultEnsureIdentityTokenTask): EnsureIdentityTokenTask
+
     @Binds
     abstract fun bindIdentityPingTask(task: DefaultIdentityPingTask): IdentityPingTask
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt
index 59fc0efbc0..e00a94297a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt
@@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
 import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse
 import im.vector.matrix.android.internal.session.room.membership.admin.UserIdAndReason
 import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody
+import im.vector.matrix.android.internal.session.room.membership.threepid.ThreePidInviteBody
 import im.vector.matrix.android.internal.session.room.relation.RelationsResponse
 import im.vector.matrix.android.internal.session.room.reporting.ReportContentBody
 import im.vector.matrix.android.internal.session.room.send.SendResponse
@@ -170,6 +171,14 @@ internal interface RoomAPI {
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite")
     fun invite(@Path("roomId") roomId: String, @Body body: InviteBody): Call<Unit>
 
+    /**
+     * Invite a user to a room, using a ThreePid
+     * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#id101
+     * @param roomId Required. The room identifier (not alias) to which to invite the user.
+     */
+    @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite")
+    fun invite3pid(@Path("roomId") roomId: String, @Body body: ThreePidInviteBody): Call<Unit>
+
     /**
      * Send a generic state events
      *
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt
index 7fa9c1526a..3eb5427b70 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt
@@ -44,6 +44,8 @@ import im.vector.matrix.android.internal.session.room.membership.joining.InviteT
 import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
 import im.vector.matrix.android.internal.session.room.membership.leaving.DefaultLeaveRoomTask
 import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
+import im.vector.matrix.android.internal.session.room.membership.threepid.DefaultInviteThreePidTask
+import im.vector.matrix.android.internal.session.room.membership.threepid.InviteThreePidTask
 import im.vector.matrix.android.internal.session.room.read.DefaultMarkAllRoomsReadTask
 import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask
 import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask
@@ -139,6 +141,9 @@ internal abstract class RoomModule {
     @Binds
     abstract fun bindInviteTask(task: DefaultInviteTask): InviteTask
 
+    @Binds
+    abstract fun bindInviteThreePidTask(task: DefaultInviteThreePidTask): InviteThreePidTask
+
     @Binds
     abstract fun bindJoinRoomTask(task: DefaultJoinRoomTask): JoinRoomTask
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
index 8467e8b46c..f413f5c9c0 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt
@@ -21,6 +21,7 @@ import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
 import com.zhuinden.monarchy.Monarchy
 import im.vector.matrix.android.api.MatrixCallback
+import im.vector.matrix.android.api.session.identity.ThreePid
 import im.vector.matrix.android.api.session.room.members.MembershipService
 import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
 import im.vector.matrix.android.api.session.room.model.Membership
@@ -36,6 +37,7 @@ import im.vector.matrix.android.internal.session.room.membership.admin.Membershi
 import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
 import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
 import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
+import im.vector.matrix.android.internal.session.room.membership.threepid.InviteThreePidTask
 import im.vector.matrix.android.internal.task.TaskExecutor
 import im.vector.matrix.android.internal.task.configureWith
 import im.vector.matrix.android.internal.util.fetchCopied
@@ -48,6 +50,7 @@ internal class DefaultMembershipService @AssistedInject constructor(
         private val taskExecutor: TaskExecutor,
         private val loadRoomMembersTask: LoadRoomMembersTask,
         private val inviteTask: InviteTask,
+        private val inviteThreePidTask: InviteThreePidTask,
         private val joinTask: JoinRoomTask,
         private val leaveRoomTask: LeaveRoomTask,
         private val membershipAdminTask: MembershipAdminTask,
@@ -152,6 +155,15 @@ internal class DefaultMembershipService @AssistedInject constructor(
                 .executeBy(taskExecutor)
     }
 
+    override fun invite3pid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
+        val params = InviteThreePidTask.Params(roomId, threePid)
+        return inviteThreePidTask
+                .configureWith(params) {
+                    this.callback = callback
+                }
+                .executeBy(taskExecutor)
+    }
+
     override fun join(reason: String?, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
         val params = JoinRoomTask.Params(roomId, reason, viaServers)
         return joinTask
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/InviteThreePidTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/InviteThreePidTask.kt
new file mode 100644
index 0000000000..25fe7b4888
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/InviteThreePidTask.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2019 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.matrix.android.internal.session.room.membership.threepid
+
+import im.vector.matrix.android.api.session.identity.IdentityServiceError
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.matrix.android.api.session.identity.toMedium
+import im.vector.matrix.android.internal.di.AuthenticatedIdentity
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.network.token.AccessTokenProvider
+import im.vector.matrix.android.internal.session.identity.EnsureIdentityTokenTask
+import im.vector.matrix.android.internal.session.identity.data.IdentityStore
+import im.vector.matrix.android.internal.session.identity.data.getIdentityServerUrlWithoutProtocol
+import im.vector.matrix.android.internal.session.room.RoomAPI
+import im.vector.matrix.android.internal.task.Task
+import org.greenrobot.eventbus.EventBus
+import javax.inject.Inject
+
+internal interface InviteThreePidTask : Task<InviteThreePidTask.Params, Unit> {
+    data class Params(
+            val roomId: String,
+            val threePid: ThreePid
+    )
+}
+
+internal class DefaultInviteThreePidTask @Inject constructor(
+        private val roomAPI: RoomAPI,
+        private val eventBus: EventBus,
+        private val identityStore: IdentityStore,
+        private val ensureIdentityTokenTask: EnsureIdentityTokenTask,
+        @AuthenticatedIdentity
+        private val accessTokenProvider: AccessTokenProvider
+) : InviteThreePidTask {
+
+    override suspend fun execute(params: InviteThreePidTask.Params) {
+        ensureIdentityTokenTask.execute(Unit)
+
+        val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol() ?: throw IdentityServiceError.NoIdentityServerConfigured
+        val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured
+
+        return executeRequest(eventBus) {
+            val body = ThreePidInviteBody(
+                    id_server = identityServerUrlWithoutProtocol,
+                    id_access_token = identityServerAccessToken,
+                    medium = params.threePid.toMedium(),
+                    address = params.threePid.value
+            )
+            apiCall = roomAPI.invite3pid(params.roomId, body)
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/ThreePidInviteBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/ThreePidInviteBody.kt
new file mode 100644
index 0000000000..23dd6bad77
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/ThreePidInviteBody.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 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.matrix.android.internal.session.room.membership.threepid
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class ThreePidInviteBody(
+        /**
+         * Required. The hostname+port of the identity server which should be used for third party identifier lookups.
+         */
+        @Json(name = "id_server") val id_server: String,
+        /**
+         * Required. An access token previously registered with the identity server. Servers can treat this as optional
+         * to distinguish between r0.5-compatible clients and this specification version.
+         */
+        @Json(name = "id_access_token") val id_access_token: String,
+        /**
+         * Required. The kind of address being passed in the address field, for example email.
+         */
+        @Json(name = "medium") val medium: String,
+        /**
+         * Required. The invitee's third party identifier.
+         */
+        @Json(name = "address") val address: String
+)
diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/BasicExtensions.kt b/vector/src/main/java/im/vector/riotx/core/extensions/BasicExtensions.kt
index 5bd6852e8a..6afd4fb279 100644
--- a/vector/src/main/java/im/vector/riotx/core/extensions/BasicExtensions.kt
+++ b/vector/src/main/java/im/vector/riotx/core/extensions/BasicExtensions.kt
@@ -19,6 +19,8 @@ package im.vector.riotx.core.extensions
 import android.os.Bundle
 import android.util.Patterns
 import androidx.fragment.app.Fragment
+import com.google.i18n.phonenumbers.NumberParseException
+import com.google.i18n.phonenumbers.PhoneNumberUtil
 
 fun Boolean.toOnOff() = if (this) "ON" else "OFF"
 
@@ -33,3 +35,16 @@ fun <T : Fragment> T.withArgs(block: Bundle.() -> Unit) = apply { arguments = Bu
  * Check if a CharSequence is an email
  */
 fun CharSequence.isEmail() = Patterns.EMAIL_ADDRESS.matcher(this).matches()
+
+/**
+ * Check if a CharSequence is a phone number
+ * FIXME It does not work?
+ */
+fun CharSequence.isMsisdn(): Boolean {
+    return try {
+        PhoneNumberUtil.getInstance().parse(this, null)
+        true
+    } catch (e: NumberParseException) {
+        false
+    }
+}
diff --git a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt
index 7c32a34aff..2b38a1ac25 100644
--- a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt
+++ b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt
@@ -17,6 +17,9 @@
 package im.vector.riotx.features.command
 
 import im.vector.matrix.android.api.MatrixPatterns
+import im.vector.matrix.android.api.session.identity.ThreePid
+import im.vector.riotx.core.extensions.isEmail
+import im.vector.riotx.core.extensions.isMsisdn
 import timber.log.Timber
 
 object CommandParser {
@@ -139,15 +142,24 @@ object CommandParser {
                     if (messageParts.size >= 2) {
                         val userId = messageParts[1]
 
-                        if (MatrixPatterns.isUserId(userId)) {
-                            ParsedCommand.Invite(
-                                    userId,
-                                    textMessage.substring(Command.INVITE.length + userId.length)
-                                            .trim()
-                                            .takeIf { it.isNotBlank() }
-                            )
-                        } else {
-                            ParsedCommand.ErrorSyntax(Command.INVITE)
+                        when {
+                            MatrixPatterns.isUserId(userId) -> {
+                                ParsedCommand.Invite(
+                                        userId,
+                                        textMessage.substring(Command.INVITE.length + userId.length)
+                                                .trim()
+                                                .takeIf { it.isNotBlank() }
+                                )
+                            }
+                            userId.isEmail()                -> {
+                                ParsedCommand.Invite3Pid(ThreePid.Email(userId))
+                            }
+                            userId.isMsisdn()               -> {
+                                ParsedCommand.Invite3Pid(ThreePid.Msisdn(userId))
+                            }
+                            else                            -> {
+                                ParsedCommand.ErrorSyntax(Command.INVITE)
+                            }
                         }
                     } else {
                         ParsedCommand.ErrorSyntax(Command.INVITE)
diff --git a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt
index 44ad2265e1..041da3dcac 100644
--- a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt
+++ b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt
@@ -16,6 +16,8 @@
 
 package im.vector.riotx.features.command
 
+import im.vector.matrix.android.api.session.identity.ThreePid
+
 /**
  * Represent a parsed command
  */
@@ -41,6 +43,7 @@ sealed class ParsedCommand {
     class UnbanUser(val userId: String, val reason: String?) : ParsedCommand()
     class SetUserPowerLevel(val userId: String, val powerLevel: Int?) : ParsedCommand()
     class Invite(val userId: String, val reason: String?) : ParsedCommand()
+    class Invite3Pid(val threePid: ThreePid) : ParsedCommand()
     class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand()
     class PartRoom(val roomAlias: String, val reason: String?) : ParsedCommand()
     class ChangeTopic(val topic: String) : ParsedCommand()
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
index 62078c3053..3c65b6281f 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
@@ -960,7 +960,7 @@ class RoomDetailFragment @Inject constructor(
                 updateComposerText("")
             }
             is RoomDetailViewEvents.SlashCommandResultError    -> {
-                displayCommandError(sendMessageResult.throwable.localizedMessage ?: getString(R.string.unexpected_error))
+                displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable))
             }
             is RoomDetailViewEvents.SlashCommandNotImplemented -> {
                 displayCommandError(getString(R.string.not_implemented))
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
index 982448d1c1..a396152f6b 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt
@@ -457,6 +457,10 @@ class RoomDetailViewModel @AssistedInject constructor(
                             handleInviteSlashCommand(slashCommandResult)
                             popDraft()
                         }
+                        is ParsedCommand.Invite3Pid               -> {
+                            handleInvite3pidSlashCommand(slashCommandResult)
+                            popDraft()
+                        }
                         is ParsedCommand.SetUserPowerLevel        -> {
                             handleSetUserPowerLevel(slashCommandResult)
                             popDraft()
@@ -678,6 +682,12 @@ class RoomDetailViewModel @AssistedInject constructor(
         }
     }
 
+    private fun handleInvite3pidSlashCommand(invite: ParsedCommand.Invite3Pid) {
+        launchSlashCommandFlow {
+            room.invite3pid(invite.threePid, it)
+        }
+    }
+
     private fun handleSetUserPowerLevel(setUserPowerLevel: ParsedCommand.SetUserPowerLevel) {
         val currentPowerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
                 ?.content