diff --git a/composer.json b/composer.json
index 61d16f6dc0eaad849b1cef3c431534175c548bce..a6eb2d676f54973419dd95c5c71f9ec547ef3dd1 100755
--- a/composer.json
+++ b/composer.json
@@ -13,7 +13,8 @@
             "History\\"               : "src/app/history/",
             "Search\\"                : "src/app/search/",
             "User\\"                  : "src/app/user/",
-            "Workflow\\"              : "src/app/workflow/"
+            "Workflow\\"              : "src/app/workflow/",
+            "Notifications\\"         : "src/app/notifications/"
     	}
     },
     "require": {
diff --git a/rest/index.php b/rest/index.php
index c55c79b7a5a5c51a98a8184a1f65c05883d6322c..3b49bd3ac83796c3882d4b4c9a052946fea6beac 100755
--- a/rest/index.php
+++ b/rest/index.php
@@ -161,4 +161,11 @@ $app->get('/workflowTemplates', \Workflow\controllers\WorkflowTemplateController
 $app->get('/workflowTemplates/{id}', \Workflow\controllers\WorkflowTemplateController::class . ':getById');
 $app->delete('/workflowTemplates/{id}', \Workflow\controllers\WorkflowTemplateController::class . ':delete');
 
+//NotificationsSchedule
+$app->post('/schedule', \Notifications\controllers\NotificationsScheduleController::class.':create');
+$app->get('/schedule', \Notifications\controllers\NotificationsScheduleController::class.':get');
+$app->get('/schedule/{id}', \Notifications\controllers\NotificationsScheduleController::class.':getById');
+$app->put('/schedule', \Notifications\controllers\NotificationsScheduleController::class.':update');
+$app->delete('/schedule', \Notifications\controllers\NotificationsScheduleController::class.':delete');
+
 $app->run();
diff --git a/sql/structure.sql b/sql/structure.sql
index 7e64bba36c93c19324da2b060ebdd64778ef2a97..29f7f0dffea0c3ce495180b218b0dc925e441cc4 100755
--- a/sql/structure.sql
+++ b/sql/structure.sql
@@ -301,3 +301,27 @@ CREATE TABLE workflow_templates_items
     CONSTRAINT workflow_templates_items_pkey PRIMARY KEY (id)
 )
 WITH (OIDS=FALSE);
+
+CREATE TABLE notifications_stack
+(
+    id serial NOT NULL,
+    main_document_id int NOT NULL,
+    type character varying(256) NOT NULL,
+    sender_id int,
+    recipient_id int,
+    CONSTRAINT external_signatory_book_pkey PRIMARY KEY (id)
+)
+WITH (OIDS=FALSE);
+
+CREATE TABLE notifications_schedule
+(
+    id serial NOT NULL,
+    type character varying(256) NOT NULL,
+    month character varying(16), -- -> 1 - 12
+    day_of_month character varying(16), -- -> null -> *, 1-31
+    day_of_week character varying(16), -- -> null -> *, 0-6 (0-Sunday, 6-Saturday)
+    hour character varying(16), -- 0 - 23
+    minute character varying(16), -- 0 - 59
+    CONSTRAINT notifications_schedule_pkey PRIMARY KEY (id)
+)
+WITH (OIDS=FALSE);
diff --git a/src/app/group/controllers/PrivilegeController.php b/src/app/group/controllers/PrivilegeController.php
index c322fd4dd56e15e8ff32db5141e9677190d649ab..a430426b4a34937bd5b14c31557152d04d946509 100755
--- a/src/app/group/controllers/PrivilegeController.php
+++ b/src/app/group/controllers/PrivilegeController.php
@@ -20,7 +20,7 @@ use Group\models\GroupPrivilegeModel;
 
 class PrivilegeController
 {
-    const PRIVILEGES = [
+    public const PRIVILEGES = [
         ['id' => 'manage_users',                'type' => 'admin', 'icon' => 'person-sharp',  'route' => '/administration/users'],
         ['id' => 'manage_groups',               'type' => 'admin', 'icon' => 'people-sharp',  'route' => '/administration/groups'],
         ['id' => 'manage_connections',          'type' => 'admin', 'icon' => 'server-sharp',  'route' => '/administration/connections'],
@@ -29,6 +29,7 @@ class PrivilegeController
         ['id' => 'manage_history',              'type' => 'admin', 'icon' => 'timer-outline', 'route' => '/administration/history'],
         ['id' => 'manage_otp_connectors',       'type' => 'admin', 'icon' => 'people-circle-outline', 'route' => '/administration/otps'],
         ['id' => 'manage_customization',        'type' => 'admin', 'icon' => 'color-wand-outline',  'route' => '/administration/customization'],
+        ['id' => 'manage_notifications',        'type' => 'admin', 'icon' => 'notifications', 'route' => '/administration/notifications'],
         ['id' => 'manage_documents',            'type' => 'simple'],
         ['id' => 'indexation',                  'type' => 'simple']
     ];
diff --git a/src/app/notifications/controllers/NotificationsScheduleController.php b/src/app/notifications/controllers/NotificationsScheduleController.php
new file mode 100644
index 0000000000000000000000000000000000000000..705008611f21247339654910711b3f20b60c0566
--- /dev/null
+++ b/src/app/notifications/controllers/NotificationsScheduleController.php
@@ -0,0 +1,186 @@
+<?php
+
+/**
+* Copyright Maarch since 2008 under license.
+* See LICENSE.txt file at the root folder for more details.
+* This file is part of Maarch software.
+*
+*/
+
+/**
+* @brief Notifications Schedule Controller
+* @author dev@maarch.org
+*/
+
+namespace Notifications\controllers;
+
+use Notifications\models\NotificationsScheduleModel;
+use Group\controllers\PrivilegeController;
+use History\controllers\HistoryController;
+use Respect\Validation\Validator;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+class NotificationsScheduleController
+{
+    public function create(Request $request, Response $response)
+    {
+        if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_notifications'])) {
+            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
+        }
+
+        $body = $request->getParsedBody();
+        // type, month, dayOfMonth, dayOfWeek, hour, minute
+
+        if (empty($body)) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body is not set or empty']);
+        } elseif (!Validator::stringType()->in(NotificationsScheduleModel::TYPES)->validate($body['type'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body type does not exist']);
+        } elseif (!Validator::oneOf(Validator::equals('ANY'), Validator::intVal()->between(1, 12))->validate($body['month'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body month is not a number or is not between 1 and 12']);
+        } elseif (!Validator::oneOf(Validator::equals('ANY'), Validator::intVal()->between(1, 31))->validate($body['dayOfMonth'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body dayOfMonth is not a number or is not between 1 and 31']);
+        } elseif (!Validator::oneOf(Validator::equals('ANY'), Validator::intVal()->between(0, 6))->validate($body['dayOfWeek'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body dayOfWeek is not a number or is not between 0 and 6']);
+        } elseif (!Validator::oneOf(Validator::equals('ANY'), Validator::intVal()->between(0, 23))->validate($body['hour'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body hour is not a number or is not between 0 and 23']);
+        } elseif (!Validator::oneOf(Validator::equals('ANY'), Validator::intVal()->between(0, 59))->validate($body['minute'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body minute is not a number or is not between 0 and 59']);
+        } elseif ($body['month'] !== 'ANY' && $body['dayOfMonth'] !== 'ANY' && !Validator::date('n-j')->validate($body['month'].'-'.$body['dayOfMonth'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body month and dayOfMonth do not form a valid date']);
+        }
+
+        $id = NotificationsScheduleModel::create([
+            'type' => $body['type'],
+            'month' => (string) $body['month'],
+            'day_of_month' => (string) $body['dayOfMonth'],
+            'day_of_week' => (string) $body['dayOfWeek'],
+            'hour' => (string) $body['hour'],
+            'minute' => (string) $body['minute'],
+        ]);
+
+        HistoryController::add([
+            'code'          => 'OK',
+            'objectType'    => 'notifications_schedule',
+            'objectId'      => $id,
+            'type'          => 'CREATION',
+            'message'       => "{notificationScheduleAdded} : {$body['type']} on {$body['month']} {$body['dayOfMonth']} {$body['dayOfWeek']} {$body['hour']} {$body['minute']}"
+        ]);
+
+        return $response->withJson(['id' => $id]);
+    }
+
+    public function get(Request $request, Response $response)
+    {
+        if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_notifications'])) {
+            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
+        }
+
+        $notificationsSchedule = NotificationsScheduleModel::get();
+
+        return $response->withJson(['notifications_schedule' => $notificationsSchedule]);
+    }
+
+    public function getById(Request $request, Response $response, array $args)
+    {
+        if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_notifications'])) {
+            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
+        }
+
+        if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Route id must be an integer']);
+        }
+
+        $notificationsScheduleItem = NotificationsScheduleModel::getById(['id' => $args['id']]);
+        if (empty($notificationsScheduleItem)) {
+            return $response->withStatus(400)->withJson(['errors' => 'Notifications schedule item not found']);
+        }
+
+        return $response->withJson(['notificationsScheduleItem' => $notificationsScheduleItem]);
+    }
+
+    public function update(Request $request, Response $response, array $aArgs)
+    {
+        if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_notifications'])) {
+            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
+        }
+
+        $body = $request->getParsedBody();
+
+        if (empty($body)) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body is not set or empty']);
+        } elseif (!Validator::intVal()->notEmpty()->validate($aArgs['id'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body id is not set or empty']);
+        } elseif (!Validator::stringType()->in(self::TYPES)->validate($body['type'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body type does not exist']);
+        } elseif (!Validator::oneOf(Validator::equals('ANY'), Validator::intVal()->between(1, 12))->validate($body['month'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body month is not a number or is not between 1 and 12']);
+        } elseif (!Validator::oneOf(Validator::equals('ANY'), Validator::intVal()->between(1, 31))->validate($body['dayOfMonth'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body dayOfMonth is not a number or is not between 1 and 31']);
+        } elseif (!Validator::oneOf(Validator::equals('ANY'), Validator::intVal()->between(0, 6))->validate($body['dayOfWeek'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body dayOfWeek is not a number or is not between 0 and 6']);
+        } elseif (!Validator::oneOf(Validator::equals('ANY'), Validator::intVal()->between(0, 23))->validate($body['hour'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body hour is not a number or is not between 0 and 23']);
+        } elseif (!Validator::oneOf(Validator::equals('ANY'), Validator::intVal()->between(0, 59))->validate($body['minute'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body minute is not a number or is not between 0 and 59']);
+        } elseif ($body['month'] !== 'ANY' && $body['dayOfMonth'] !== 'ANY' && !Validator::date('n-j')->validate($body['month'].'-'.$body['dayOfMonth'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Body month and dayOfMonth do not form a valid date']);
+        }
+
+        $notificationsScheduleItem::getById(['id' => $aArgs['id']]);
+        if (empty($notificationsScheduleItem)) {
+            return $response->withStatus(400)->withJson(['errors' => 'Notifications schedule item not found']);
+        }
+
+        NotificationsScheduleModel::update([
+            'set' => [
+                'type' => $body['type'],
+                'month' => (string) $body['month'],
+                'day_of_month' => (string) $body['dayOfMonth'],
+                'day_of_week' => (string) $body['dayOfWeek'],
+                'hour' => (string) $body['hour'],
+                'minute' => (string) $body['minute'],
+            ],
+            'where' => ['id = ?'],
+            'data'  => [$aArgs['id']]
+        ]);
+
+        HistoryController::add([
+            'code'       => 'OK',
+            'objectType' => 'notifications_schedule',
+            'objectId'   => $aArgs['id'],
+            'type'       => 'MODIFICATION',
+            'message'    => "{notificationsScheduleItemUpdated} : {$body['type']}"
+        ]);
+
+        return $response->withStatus(204);
+    }
+
+    public function delete(Request $request, Response $response, array $aArgs)
+    {
+        if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_notifications'])) {
+            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
+        }
+
+        if (!Validator::intVal()->notEmpty()->validate($aArgs['id'])) {
+            return $response->withStatus(400)->withJson(['errors' => 'Id must be an integer']);
+        }
+
+        $notificationsScheduleItem = NotificationsScheduleModel::getById(['id' => $aArgs['id']]);
+        if (empty($notificationsScheduleItem)) {
+            return $response->withStatus(400)->withJson(['errors' => 'Notifications schedule item not found']);
+        }
+
+        NotificationsScheduleModel::delete(['where' => ['id = ?'], 'data' => [$aArgs['id']]]);
+
+        HistoryController::add([
+            'code'          => 'OK',
+            'objectType'    => 'notifications_schedule',
+            'objectId'      => $aArgs['id'],
+            'type'          => 'SUPPRESSION',
+            'message'       => "{notificationsScheduleItemDeleted} : {$notificationsScheduleItem['type']}"
+        ]);
+
+        return $response->withStatus(204);
+    }
+}
diff --git a/src/app/notifications/models/NotificationsScheduleModel.php b/src/app/notifications/models/NotificationsScheduleModel.php
new file mode 100644
index 0000000000000000000000000000000000000000..3e8c133df1088f46825f7d46cc96facfb67f36ee
--- /dev/null
+++ b/src/app/notifications/models/NotificationsScheduleModel.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+* Copyright Maarch since 2008 under license.
+* See LICENSE.txt file at the root folder for more details.
+* This file is part of Maarch software.
+*
+*/
+
+/**
+* @brief Notifications Schedule Model
+* @author dev@maarch.org
+*/
+
+namespace Notifications\models;
+
+use SrcCore\models\DatabaseModel;
+use SrcCore\models\ValidatorModel;
+
+class NotificationsScheduleModel
+{
+    public const TYPES = ['next_user', 'typist_END', 'typist_INT', 'typist_DEL', 'typist_REF'];
+
+    public static function get(array $aArgs = [])
+    {
+        ValidatorModel::arrayType($aArgs, ['select', 'where', 'data', 'orderBy']);
+        ValidatorModel::intType($aArgs, ['limit']);
+
+        $notificationsSchedule = DatabaseModel::select([
+            'select'    => empty($aArgs['select']) ? ['*'] : $aArgs['select'],
+            'table'     => ['notifications_schedule'],
+            'where'     => empty($aArgs['where']) ? [] : $aArgs['where'],
+            'data'      => empty($aArgs['data']) ? [] : $aArgs['data'],
+            'orderBy'   => empty($aArgs['orderBy']) ? [] : $aArgs['orderBy'],
+            'limit'     => empty($aArgs['limit']) ? 0 : $aArgs['limit']
+        ]);
+
+        return $notificationsSchedule;
+    }
+
+    public static function getById(array $aArgs)
+    {
+        ValidatorModel::notEmpty($aArgs, ['id']);
+        ValidatorModel::intVal($aArgs, ['id']);
+        ValidatorModel::arrayType($aArgs, ['select']);
+
+        $notificationsScheduleItem = NotificationsScheduleModel::get([
+            'select'    => empty($aArgs['select']) ? ['*'] : $aArgs['select'],
+            'where'     => ['id = ?'],
+            'data'      => [$aArgs['id']]
+        ]);
+
+        if (!empty($notificationsScheduleItem)) {
+            return $notificationsScheduleItem[0];
+        }
+
+        return [];
+    }
+
+    public static function create(array $aArgs)
+    {
+        ValidatorModel::notEmpty($aArgs, ['label']);
+        ValidatorModel::stringType($aArgs, ['type', 'month', 'day_of_month', 'day_of_week', 'hour', 'minute']);
+
+        $nextSequenceId = DatabaseModel::getNextSequenceValue(['sequenceId' => 'notifications_schedule_id_seq']);
+        DatabaseModel::insert([
+            'table'         => 'notifications_schedule',
+            'columnsValues' => [
+                'id'    => $nextSequenceId,
+                'type'  => $aArgs['type'],
+                'month' => $aArgs['month'],
+                'day_of_month' => $aArgs['day_of_month'],
+                'day_of_week' => $aArgs['day_of_week'],
+                'hour' => $aArgs['hour'],
+                'minute' => $aArgs['minute'],
+            ]
+        ]);
+
+        return $nextSequenceId;
+    }
+
+    public static function update(array $args)
+    {
+        ValidatorModel::notEmpty($args, ['set', 'where', 'data']);
+        ValidatorModel::arrayType($args, ['set', 'where', 'data']);
+
+        DatabaseModel::update([
+            'table' => 'notifications_schedule',
+            'set'   => $args['set'],
+            'where' => $args['where'],
+            'data'  => $args['data']
+        ]);
+
+        return true;
+    }
+
+    public static function delete(array $args)
+    {
+        ValidatorModel::notEmpty($args, ['where', 'data']);
+        ValidatorModel::arrayType($args, ['where', 'data']);
+
+        DatabaseModel::delete([
+            'table' => 'notifications_schedule',
+            'where' => $args['where'],
+            'data'  => $args['data']
+        ]);
+
+        return true;
+    }
+}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
index f27399a042d95c4708af3a8c74d35d338763cf8f..62ecfd8d0046b60517ea7370300f52744f1ab85d 100644
--- a/vendor/composer/LICENSE
+++ b/vendor/composer/LICENSE
@@ -1,4 +1,3 @@
-
 Copyright (c) Nils Adermann, Jordi Boggiano
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -18,4 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
-
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
index 3c823bd1dd35b577452af9f487355d1449e3be74..1b2f5d935117ccd3d9f77dc269b583516625cbc3 100644
--- a/vendor/composer/autoload_psr4.php
+++ b/vendor/composer/autoload_psr4.php
@@ -26,6 +26,7 @@ return array(
     'ParagonIE\\ConstantTime\\' => array($vendorDir . '/paragonie/constant_time_encoding/src'),
     'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
     'Otp\\' => array($vendorDir . '/christian-riesen/otp/src'),
+    'Notifications\\' => array($baseDir . '/src/app/notifications'),
     'History\\' => array($baseDir . '/src/app/history'),
     'Group\\' => array($baseDir . '/src/app/group'),
     'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
index ea707cfbbd8e5f244eaa2bea7abf9e114c844b42..c1322e0a7315f20523aa33a51533f3d58d239d18 100644
--- a/vendor/composer/autoload_static.php
+++ b/vendor/composer/autoload_static.php
@@ -60,6 +60,10 @@ class ComposerStaticInit637514d10f1ed5d4c55a005a428a3656
         array (
             'Otp\\' => 4,
         ),
+        'N' => 
+        array (
+            'Notifications\\' => 14,
+        ),
         'H' => 
         array (
             'History\\' => 8,
@@ -179,6 +183,10 @@ class ComposerStaticInit637514d10f1ed5d4c55a005a428a3656
         array (
             0 => __DIR__ . '/..' . '/christian-riesen/otp/src',
         ),
+        'Notifications\\' => 
+        array (
+            0 => __DIR__ . '/../..' . '/src/app/notifications',
+        ),
         'History\\' => 
         array (
             0 => __DIR__ . '/../..' . '/src/app/history',