diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
index 2203902f3f..d8d550b46c 100644
--- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
+++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
@@ -8,7 +8,9 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import im.vector.matrix.android.api.Matrix
+import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.api.session.events.model.EnrichedEvent
+import im.vector.matrix.android.api.session.events.model.Event
 import im.vector.matrix.android.api.session.room.Room
 import im.vector.matrix.android.api.session.room.model.RoomSummary
 import im.vector.riotredesign.R
@@ -58,7 +60,9 @@ class RoomDetailFragment : RiotFragment() {
             val textMessage = composerEditText.text.toString()
             if (textMessage.isNotBlank()) {
                 composerEditText.text = null
-                room.sendTextMessage(textMessage)
+                room.sendTextMessage(textMessage, object : MatrixCallback<Event> {
+
+                })
             }
         }
     }
diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
index 514e139df8..5f5810bba7 100644
--- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
@@ -58,7 +58,7 @@ class TimelineEventController(private val roomId: String,
 
             val item = when (event.root.type) {
                 EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, addDaySeparator, date)
-                else              -> textItemFactory.create(event)
+                else -> textItemFactory.create(event)
             }
             item
                     ?.onBind { timeline?.loadAround(index) }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt
index d9ebb1ecbf..04efa9351d 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt
@@ -6,7 +6,7 @@ import com.squareup.moshi.Types
 import im.vector.matrix.android.internal.di.MoshiProvider
 import java.lang.reflect.ParameterizedType
 
-typealias Content = Map<String, Any>
+typealias Content = Map<String, @JvmSuppressWildcards Any>
 
 @JsonClass(generateAdapter = true)
 data class Event(
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/SendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/SendService.kt
index d4b085cb71..6fbf87dbc8 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/SendService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/SendService.kt
@@ -1,10 +1,12 @@
 package im.vector.matrix.android.api.session.room
 
+import im.vector.matrix.android.api.MatrixCallback
+import im.vector.matrix.android.api.session.events.model.Event
 import im.vector.matrix.android.api.util.Cancelable
 
 interface SendService {
 
-    fun sendTextMessage(text: String): Cancelable
+    fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable
 
 
 }
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/EventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/EventFactory.kt
new file mode 100644
index 0000000000..2dafe5036b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/EventFactory.kt
@@ -0,0 +1,44 @@
+package im.vector.matrix.android.api.session.room.send
+
+import im.vector.matrix.android.api.auth.data.Credentials
+import im.vector.matrix.android.api.session.events.model.Content
+import im.vector.matrix.android.api.session.events.model.Event
+import im.vector.matrix.android.api.session.events.model.EventType
+import im.vector.matrix.android.api.session.room.model.MessageContent
+import im.vector.matrix.android.api.session.room.model.MessageType
+import im.vector.matrix.android.internal.di.MoshiProvider
+
+internal class EventFactory(private val credentials: Credentials) {
+
+    private val moshi = MoshiProvider.providesMoshi()
+
+    fun createTextEvent(roomId: String, text: String): Event {
+        val content = MessageContent(type = MessageType.MSGTYPE_TEXT, body = text)
+
+        return Event(
+                roomId = roomId,
+                originServerTs = dummyOriginServerTs(),
+                sender = credentials.userId,
+                eventId = dummyEventId(roomId),
+                type = EventType.MESSAGE,
+                content = toContent(content)
+        )
+    }
+
+    private fun dummyOriginServerTs(): Long {
+        return System.currentTimeMillis()
+    }
+
+    private fun dummyEventId(roomId: String): String {
+        return roomId + "-" + dummyOriginServerTs()
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    private inline fun <reified T> toContent(data: T?): Content? {
+        val moshiAdapter = moshi.adapter(T::class.java)
+        val jsonValue = moshiAdapter.toJsonValue(data)
+        return jsonValue as? Content?
+    }
+
+
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt
index 03b4b1e848..15ba170e9f 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt
@@ -35,6 +35,10 @@ internal fun ChunkEntity.addAll(events: List<Event>,
     }
 }
 
+internal fun ChunkEntity.updateDisplayIndexes() {
+    events.forEachIndexed { index, eventEntity -> eventEntity.displayIndex = index }
+}
+
 internal fun ChunkEntity.addOrUpdate(event: Event,
                                      direction: PaginationDirection,
                                      stateIndexOffset: Int = 0) {
@@ -69,7 +73,7 @@ internal fun ChunkEntity.addOrUpdate(event: Event,
 
 internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
     return when (direction) {
-               PaginationDirection.FORWARDS  -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex
-               PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex
-           } ?: defaultValue
+        PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex
+        PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex
+    } ?: defaultValue
 }
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt
index 0b69f2c0cd..07018c3523 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt
@@ -13,7 +13,7 @@ internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
 }
 
 internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) {
-    chunkEntity.events.forEachIndexed { index, eventEntity -> eventEntity.displayIndex = index }
+    chunkEntity.updateDisplayIndexes()
     if (!chunks.contains(chunkEntity)) {
         chunks.add(chunkEntity)
     }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt
index 8f2d1ed98a..e4b2e93c29 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt
@@ -23,6 +23,10 @@ internal class SessionModule(private val sessionParams: SessionParams) : Module
 
     override fun invoke(): ModuleDefinition = module(override = true) {
 
+        scope(DefaultSession.SCOPE) {
+            sessionParams
+        }
+
         scope(DefaultSession.SCOPE) {
             RealmConfiguration.Builder()
                     .name(sessionParams.credentials.userId)
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt
index 07b8a9fe5d..766fea9dd6 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt
@@ -6,6 +6,7 @@ import android.arch.paging.PagedList
 import com.zhuinden.monarchy.Monarchy
 import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.api.session.events.model.EnrichedEvent
+import im.vector.matrix.android.api.session.events.model.Event
 import im.vector.matrix.android.api.session.room.Room
 import im.vector.matrix.android.api.session.room.SendService
 import im.vector.matrix.android.api.session.room.TimelineHolder
@@ -61,14 +62,14 @@ internal data class DefaultRoom(
 
     private fun areAllMembersLoaded(): Boolean {
         return monarchy
-                       .fetchAllCopiedSync { RoomEntity.where(it, roomId) }
-                       .firstOrNull()
-                       ?.areAllMembersLoaded ?: false
+                .fetchAllCopiedSync { RoomEntity.where(it, roomId) }
+                .firstOrNull()
+                ?.areAllMembersLoaded ?: false
     }
 
 
-    override fun sendTextMessage(text: String): Cancelable {
-        return sendService.sendTextMessage(text)
+    override fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable {
+        return sendService.sendTextMessage(text, callback)
     }
 
 }
\ No newline at end of file
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 31311bcdab..28c9776fb0 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
@@ -1,5 +1,6 @@
 package im.vector.matrix.android.internal.session.room
 
+import im.vector.matrix.android.api.session.events.model.Content
 import im.vector.matrix.android.api.session.events.model.Event
 import im.vector.matrix.android.api.session.room.model.MessageContent
 import im.vector.matrix.android.internal.network.NetworkConstants
@@ -62,7 +63,7 @@ internal interface RoomAPI {
     fun send(@Path("txId") txId: String,
              @Path("roomId") roomId: String,
              @Path("eventType") eventType: String,
-             @Body content: MessageContent
+             @Body content: Content?
     ): Call<SendResponse>
 
     /**
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 14dee22b06..598bd5f087 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
@@ -1,7 +1,9 @@
 package im.vector.matrix.android.internal.session.room
 
+import im.vector.matrix.android.api.auth.data.SessionParams
 import im.vector.matrix.android.api.session.room.SendService
 import im.vector.matrix.android.api.session.room.TimelineHolder
+import im.vector.matrix.android.api.session.room.send.EventFactory
 import im.vector.matrix.android.internal.session.DefaultSession
 import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersRequest
 import im.vector.matrix.android.internal.session.room.send.DefaultSendService
@@ -39,9 +41,14 @@ class RoomModule : Module {
             DefaultTimelineHolder(roomId, get(), timelineBoundaryCallback) as TimelineHolder
         }
 
+        scope(DefaultSession.SCOPE) {
+            val sessionParams = get<SessionParams>()
+            EventFactory(sessionParams.credentials)
+        }
+
         factory {
             val roomId: String = it[0]
-            DefaultSendService(roomId) as SendService
+            DefaultSendService(roomId, get(), get()) as SendService
         }
 
     }.invoke()
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
index 015d5858cf..57e1fc6c9b 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt
@@ -1,23 +1,43 @@
 package im.vector.matrix.android.internal.session.room.send
 
 import androidx.work.*
+import com.zhuinden.monarchy.Monarchy
+import im.vector.matrix.android.api.MatrixCallback
+import im.vector.matrix.android.api.session.events.model.Event
 import im.vector.matrix.android.api.session.room.SendService
+import im.vector.matrix.android.api.session.room.send.EventFactory
 import im.vector.matrix.android.api.util.Cancelable
+import im.vector.matrix.android.internal.database.helper.addOrUpdate
+import im.vector.matrix.android.internal.database.helper.updateDisplayIndexes
+import im.vector.matrix.android.internal.database.model.ChunkEntity
+import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
+import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
 import im.vector.matrix.android.internal.util.CancelableWork
 import im.vector.matrix.android.internal.util.WorkerParamsFactory
+import im.vector.matrix.android.internal.util.tryTransactionAsync
 import java.util.concurrent.TimeUnit
 
 private const val SEND_WORK = "SEND_WORK"
 
-internal class DefaultSendService(private val roomId: String) : SendService {
+internal class DefaultSendService(private val roomId: String,
+                                  private val eventFactory: EventFactory,
+                                  private val monarchy: Monarchy) : SendService {
 
     private val sendConstraints = Constraints.Builder()
             .setRequiredNetworkType(NetworkType.CONNECTED)
             .build()
 
-    override fun sendTextMessage(text: String): Cancelable {
+    override fun sendTextMessage(text: String, callback: MatrixCallback<Event>): Cancelable {
+        val event = eventFactory.createTextEvent(roomId, text)
 
-        val sendContentWorkerParams = SendEventWorker.Params(roomId, text)
+        monarchy.tryTransactionAsync { realm ->
+            val chunkEntity = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
+                    ?: return@tryTransactionAsync
+            chunkEntity.addOrUpdate(event, PaginationDirection.FORWARDS)
+            chunkEntity.updateDisplayIndexes()
+        }
+
+        val sendContentWorkerParams = SendEventWorker.Params(roomId, event)
         val workData = WorkerParamsFactory.toData(sendContentWorkerParams)
 
         val sendWork = OneTimeWorkRequestBuilder<SendEventWorker>()
@@ -34,4 +54,4 @@ internal class DefaultSendService(private val roomId: String) : SendService {
 
     }
 
-}
\ No newline at end of file
+}
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt
index f490beb175..946b567cc7 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendEventWorker.kt
@@ -4,12 +4,14 @@ import android.content.Context
 import androidx.work.Worker
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
-import im.vector.matrix.android.api.session.events.model.EventType
-import im.vector.matrix.android.api.session.room.model.MessageContent
-import im.vector.matrix.android.api.session.room.model.MessageType
+import com.zhuinden.monarchy.Monarchy
+import im.vector.matrix.android.api.session.events.model.Event
+import im.vector.matrix.android.internal.database.model.EventEntity
+import im.vector.matrix.android.internal.database.query.where
 import im.vector.matrix.android.internal.network.executeRequest
 import im.vector.matrix.android.internal.session.room.RoomAPI
 import im.vector.matrix.android.internal.util.WorkerParamsFactory
+import im.vector.matrix.android.internal.util.tryTransactionSync
 import org.koin.standalone.KoinComponent
 import org.koin.standalone.inject
 
@@ -20,27 +22,37 @@ internal class SendEventWorker(context: Context, params: WorkerParameters)
     @JsonClass(generateAdapter = true)
     internal data class Params(
             val roomId: String,
-            val text: String
+            val event: Event
     )
 
     private val roomAPI by inject<RoomAPI>()
+    private val monarchy by inject<Monarchy>()
 
     override fun doWork(): Result {
 
-        val sendWorkerParameters = WorkerParamsFactory.fromData<Params>(inputData)
+        val params = WorkerParamsFactory.fromData<Params>(inputData)
                 ?: return Result.FAILURE
 
-        val fakeId = sendWorkerParameters.roomId + "-" + System.currentTimeMillis()
+        if (params.event.eventId == null) {
+            return Result.FAILURE
+        }
+
         val result = executeRequest<SendResponse> {
             apiCall = roomAPI.send(
-                    fakeId,
-                    sendWorkerParameters.roomId,
-                    EventType.MESSAGE,
-                    MessageContent(MessageType.MSGTYPE_TEXT, sendWorkerParameters.text)
+                    params.event.eventId,
+                    params.roomId,
+                    params.event.type,
+                    params.event.content
             )
         }
+        result.flatMap { sendResponse ->
+            monarchy.tryTransactionSync { realm ->
+                val dummyEventEntity = EventEntity.where(realm, params.event.eventId).findFirst()
+                dummyEventEntity?.eventId = sendResponse.eventId
+            }
+        }
         return result.fold({ Result.RETRY }, { Result.SUCCESS })
     }
 
 
-}
\ No newline at end of file
+}