diff --git a/build.gradle b/build.gradle
index 423b937..fd52fa2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -25,6 +25,7 @@ dependencies {
     implementation libs.plagubot
 
     implementation project(":introduction")
+    implementation project(":welcome")
 }
 
 application {
diff --git a/config.json b/config.json
index 6878d1e..483d3a6 100644
--- a/config.json
+++ b/config.json
@@ -1,12 +1,15 @@
 {
   "botToken": "1234567890:ABCDEFGHIJKLMNOP_qrstuvwxyz12345678",
   "plugins": [
-    "IntroductionPlugin"
+    "dev.inmo.plagubot.plugins.commands.CommandsPlugin",
+
+    "IntroductionPlugin",
+    "WelcomePlugin"
   ],
   "introduction": {
     "onStartCommandMessage": "Hello World"
   },
   "database": {
-    "url": "jdbc:sqlite:file:local.db?mode=memory&cache=shared"
+    "url": "jdbc:sqlite:file:local.db"
   }
 }
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5857ec4..3b62b06 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,14 +1,16 @@
 [versions]
 
 kotlin = "1.6.21"
-plagubot = "1.2.2"
+plagubot = "1.2.3"
 kslog = "0.3.2"
+plagubot-commands = "0.1.0"
 
 [libraries]
 
 kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
 plagubot = { module = "dev.inmo:plagubot.bot", version.ref = "plagubot" }
 plagubot-plugin = { module = "dev.inmo:plagubot.plugin", version.ref = "plagubot" }
+plagubot-commands = { module = "dev.inmo:plagubot.plugins.commands", version.ref = "plagubot-commands" }
 kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
 
 # Libs for classpath
diff --git a/settings.gradle b/settings.gradle
index 8fd4ae8..ecd58c9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,4 @@
 rootProject.name = "tutorial"
 
 include ":introduction"
+include ":welcome"
diff --git a/welcome/build.gradle b/welcome/build.gradle
index 3b4d2f0..ddb46de 100644
--- a/welcome/build.gradle
+++ b/welcome/build.gradle
@@ -23,4 +23,5 @@ dependencies {
     implementation libs.kotlin
     api libs.plagubot.plugin
     api libs.kslog
+    api libs.plagubot.commands
 }
diff --git a/welcome/src/main/kotlin/UserAdminChecker.kt b/welcome/src/main/kotlin/UserAdminChecker.kt
new file mode 100644
index 0000000..9217c19
--- /dev/null
+++ b/welcome/src/main/kotlin/UserAdminChecker.kt
@@ -0,0 +1,11 @@
+import dev.inmo.tgbotapi.bot.TelegramBot
+import dev.inmo.tgbotapi.extensions.api.chat.get.getChatAdministrators
+import dev.inmo.tgbotapi.types.chat.GroupChat
+import dev.inmo.tgbotapi.types.chat.User
+
+suspend fun TelegramBot.userIsAdmin(user: User, chat: GroupChat): Boolean {
+    val chatAdmins = getChatAdministrators(chat)
+    val chatAdminsIds = chatAdmins.map { adminMember -> adminMember.user.id }
+
+    return user.id in chatAdminsIds
+}
diff --git a/welcome/src/main/kotlin/WelcomePlugin.kt b/welcome/src/main/kotlin/WelcomePlugin.kt
index 5998a2a..c795240 100644
--- a/welcome/src/main/kotlin/WelcomePlugin.kt
+++ b/welcome/src/main/kotlin/WelcomePlugin.kt
@@ -1,17 +1,40 @@
+import db.WelcomeTable
 import dev.inmo.kslog.common.logger
 import dev.inmo.kslog.common.w
+import dev.inmo.micro_utils.coroutines.runCatchingSafely
+import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
 import dev.inmo.plagubot.Plugin
-import dev.inmo.tgbotapi.extensions.api.send.reply
-import dev.inmo.tgbotapi.extensions.api.send.sendMessage
+import dev.inmo.plagubot.plugins.commands.full
+import dev.inmo.tgbotapi.bot.exceptions.RequestException
+import dev.inmo.tgbotapi.extensions.api.answers.answer
+import dev.inmo.tgbotapi.extensions.api.chat.get.getChatAdministrators
+import dev.inmo.tgbotapi.extensions.api.edit.edit
+import dev.inmo.tgbotapi.extensions.api.send.*
 import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
-import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
-import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMyChatMemberUpdated
-import dev.inmo.tgbotapi.types.chat.PrivateChat
+import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage
+import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery
+import dev.inmo.tgbotapi.extensions.behaviour_builder.oneOf
+import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
+import dev.inmo.tgbotapi.extensions.utils.*
+import dev.inmo.tgbotapi.extensions.utils.formatting.*
+import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
+import dev.inmo.tgbotapi.types.BotCommand
+import dev.inmo.tgbotapi.types.MilliSeconds
+import dev.inmo.tgbotapi.types.chat.GroupChat
+import dev.inmo.tgbotapi.types.commands.BotCommandScope
+import dev.inmo.tgbotapi.types.message.abstracts.CommonGroupContentMessage
+import dev.inmo.tgbotapi.types.message.content.MessageContent
+import dev.inmo.tgbotapi.types.message.content.TextContent
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.json.*
+import model.ChatSettings
 import org.jetbrains.exposed.sql.Database
 import org.koin.core.Koin
 import org.koin.core.module.Module
+import org.koin.core.qualifier.named
 
 /**
  * This is template of plugin with preset [log]ger, [Config] and template configurations of [setupDI] and [setupBotPlugin].
@@ -30,27 +53,182 @@ class WelcomePlugin : Plugin {
      * See realization of [setupDI] to get know how this class will be deserialized from global config
      *
      * See realization of [setupBotPlugin] to get know how to get access to this class
+     *
+     * @param recheckOfAdmin This parameter will be used before setup of
      */
     @Serializable
     private class Config(
+        val recheckOfAdmin: MilliSeconds = 60L
     )
 
     /**
      * DI configuration of current plugin. Here we are decoding [Config] and put it into [Module] receiver
      */
     override fun Module.setupDI(database: Database, params: JsonObject) {
-        single { get<Json>().decodeFromJsonElement(Config.serializer(), params[pluginConfigSectionName] ?: return@single null) }
+        single { get<Json>().decodeFromJsonElement(Config.serializer(), params[pluginConfigSectionName] ?: return@single Config()) }
+        single { WelcomeTable(database) }
+        single(named("welcome")) { BotCommand("welcome", "Use to setup welcome message").full(BotCommandScope.AllChatAdministrators) }
+    }
+
+    private suspend fun BehaviourContext.handleWelcomeCommand(
+        welcomeTable: WelcomeTable,
+        config: Config,
+        groupMessage: CommonGroupContentMessage<MessageContent>
+    ) {
+        val user = groupMessage.user
+
+        if (userIsAdmin(user, groupMessage.chat)) {
+            val cancelData = "cancel_${groupMessage.chat.id}"
+            val unsetData = "unset_${groupMessage.chat.id}"
+
+            val sentMessage = sendMessage(
+                user,
+                buildEntities {
+                    regular("Ok, send me the message which should be used as welcome message for chat ")
+                    underline(groupMessage.chat.title)
+                },
+                replyMarkup = inlineKeyboard {
+                    row {
+                        dataButton("Unset", unsetData)
+                        dataButton("Cancel", cancelData)
+                    }
+                }
+            )
+
+            oneOf(
+                async {
+                    val query = waitMessageDataCallbackQuery().filter {
+                        it.data == unsetData
+                            && it.message.chat.id == sentMessage.chat.id
+                            && it.message.messageId == sentMessage.messageId
+                    }.first()
+
+                    if (welcomeTable.unset(groupMessage.chat.id)) {
+                        edit(
+                            sentMessage,
+                            buildEntities {
+                                regular("Welcome message has been removed for chat ")
+                                underline(groupMessage.chat.title)
+                            }
+                        )
+                    } else {
+                        edit(
+                            sentMessage,
+                            buildEntities {
+                                regular("Something went wrong on welcome message unsetting for chat ")
+                                underline(groupMessage.chat.title)
+                            }
+                        )
+                    }
+
+                    answer(query)
+                },
+                async {
+                    val query = waitMessageDataCallbackQuery().filter {
+                        it.data == cancelData
+                            && it.message.chat.id == sentMessage.chat.id
+                            && it.message.messageId == sentMessage.messageId
+                    }.first()
+
+                    edit(
+                        sentMessage,
+                        buildEntities {
+                            regular("You have cancelled change of welcome message for chat ")
+                            underline(groupMessage.chat.title)
+                        }
+                    )
+
+                    answer(query)
+                },
+                async {
+                    val message = waitContentMessage().filter {
+                        it.chat.id == sentMessage.chat.id
+                    }.first()
+
+                    val success = welcomeTable.set(
+                        ChatSettings(
+                            groupMessage.chat.id,
+                            message.chat.id,
+                            message.messageId
+                        )
+                    )
+
+                    if (success) {
+                        reply(
+                            message,
+                            buildEntities {
+                                regular("Welcome message has been changed for chat ")
+                                underline(groupMessage.chat.title)
+                                regular(".\n\n")
+                                bold("Please, do not delete this message if you want it to work and don't stop this bot to keep welcome message works right")
+                            }
+                        )
+                    } else {
+                        reply(
+                            message,
+                            buildEntities {
+                                regular("Something went wrong on welcome message changing for chat ")
+                                underline(groupMessage.chat.title)
+                            }
+                        )
+                    }
+                },
+                async {
+                    while (isActive) {
+                        delay(config.recheckOfAdmin)
+
+                        if (!userIsAdmin(user, groupMessage.chat)) {
+                            edit(sentMessage, "Sorry, but you are not admin in chat ${groupMessage.chat.title} more")
+                            break
+                        }
+                    }
+                }
+            )
+        }
     }
 
     /**
      * Final configuration of bot. Here we are getting [Config] from [koin]
      */
     override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
-        val config = koin.getOrNull<Config>()
+        val config = koin.get<Config>()
 
-        if (config == null) {
-            log.w("Plugin has been disabled due to absence of \"$pluginConfigSectionName\" field in config or some error during configuration loading")
-            return
+        val welcomeTable = koin.get<WelcomeTable>()
+
+        onCommand(
+            "welcome",
+            initialFilter = {
+                it.chat is GroupChat
+            }
+        ) {
+            it.whenCommonGroupContentMessage { groupMessage ->
+                launch {
+                    handleWelcomeCommand(welcomeTable, config, groupMessage)
+                }
+            }
+        }
+
+        onNewChatMembers {
+            val chatSettings = welcomeTable.get(it.chat.id)
+
+            if (chatSettings == null) {
+                return@onNewChatMembers
+            }
+
+            try {
+                copyMessage(
+                    it.chat.id,
+                    chatSettings.sourceChatId,
+                    chatSettings.sourceMessageId
+                )
+            } catch (e: RequestException) {
+                welcomeTable.unset(it.chat.id)
+            }
+        }
+
+
+        allUpdatesFlow.subscribeSafelyWithoutExceptions(scope) {
+            println(it)
         }
     }
 
diff --git a/welcome/src/main/kotlin/db/WelcomeTable.kt b/welcome/src/main/kotlin/db/WelcomeTable.kt
new file mode 100644
index 0000000..62a3de1
--- /dev/null
+++ b/welcome/src/main/kotlin/db/WelcomeTable.kt
@@ -0,0 +1,44 @@
+package db
+
+import dev.inmo.micro_utils.repos.exposed.ExposedRepo
+import dev.inmo.micro_utils.repos.exposed.initTable
+import dev.inmo.tgbotapi.types.ChatId
+import model.ChatSettings
+import org.jetbrains.exposed.sql.*
+import org.jetbrains.exposed.sql.transactions.transaction
+
+internal class WelcomeTable(
+    override val database: Database
+) : Table("welcome"), ExposedRepo {
+    val targetChatIdColumn = long("targetChatId").uniqueIndex()
+    val sourceChatIdColumn = long("sourceChatId")
+    val sourceMessageIdColumn = long("sourceMessageId")
+    override val primaryKey: PrimaryKey = PrimaryKey(targetChatIdColumn)
+
+    init {
+        initTable()
+    }
+
+    fun get(chatId: ChatId): ChatSettings? = transaction(database) {
+        select { targetChatIdColumn.eq(chatId.chatId) }.limit(1).firstOrNull() ?.let {
+            ChatSettings(
+                ChatId(it[targetChatIdColumn]),
+                ChatId(it[sourceChatIdColumn]),
+                it[sourceMessageIdColumn]
+            )
+        }
+    }
+
+    fun set(chatSettings: ChatSettings): Boolean = transaction(database) {
+        deleteWhere { targetChatIdColumn.eq(chatSettings.targetChatId.chatId) }
+        insert {
+            it[targetChatIdColumn] = chatSettings.targetChatId.chatId
+            it[sourceChatIdColumn] = chatSettings.sourceChatId.chatId
+            it[sourceMessageIdColumn] = chatSettings.sourceMessageId
+        }.insertedCount > 0
+    }
+
+    fun unset(chatId: ChatId): Boolean = transaction(database) {
+        deleteWhere { targetChatIdColumn.eq(chatId.chatId) } > 0
+    }
+}
diff --git a/welcome/src/main/kotlin/model/ChatSettings.kt b/welcome/src/main/kotlin/model/ChatSettings.kt
new file mode 100644
index 0000000..17e668f
--- /dev/null
+++ b/welcome/src/main/kotlin/model/ChatSettings.kt
@@ -0,0 +1,12 @@
+package model
+
+import dev.inmo.tgbotapi.types.ChatId
+import dev.inmo.tgbotapi.types.MessageIdentifier
+import kotlinx.serialization.Serializable
+
+@Serializable
+internal data class ChatSettings(
+    val targetChatId: ChatId,
+    val sourceChatId: ChatId,
+    val sourceMessageId: MessageIdentifier
+)