diff --git a/src/app/registeredMail/controllers/RegisteredMailTrait.php b/src/app/registeredMail/controllers/RegisteredMailTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..d4e711bce2ecfcc4acb894c7b846ec4cbd10f902
--- /dev/null
+++ b/src/app/registeredMail/controllers/RegisteredMailTrait.php
@@ -0,0 +1,138 @@
+<?php
+
+/**
+* Copyright Maarch since 2008 under licence GPLv3.
+* See LICENCE.txt file at the root folder for more details.
+* This file is part of Maarch software.
+
+* @brief   AcknowledgementReceiptTrait
+* @author  dev <dev@maarch.org>
+* @ingroup core
+*/
+
+namespace RegisteredMail\controllers;
+
+use RegisteredMail\models\IssuingSiteModel;
+use RegisteredMail\models\RegisteredMailModel;
+use RegisteredMail\models\RegisteredNumberRangeModel;
+use Resource\models\ResModel;
+use SrcCore\models\ValidatorModel;
+
+trait RegisteredMailTrait
+{
+    public static function saveRegisteredMail(array $args)
+    {
+        ValidatorModel::notEmpty($args, ['resId', 'data']);
+        ValidatorModel::intVal($args, ['resId']);
+        ValidatorModel::arrayType($args, ['data']);
+
+        $resource = ResModel::getById(['select' => ['departure_date'], 'resId' => $args['resId']]);
+        if (empty($resource['departure_date'])) {
+            return ['errors' => ['Departure date is empty']];
+        }
+
+        if (!in_array($args['data']['type'], ['2D', '2C', 'RW'])) {
+            return ['errors' => ['Type is not correct']];
+        } elseif (!in_array($args['data']['warranty'], ['R1', 'R2', 'R3'])) {
+            return ['errors' => ['Type is not correct']];
+        } elseif ($args['data']['type'] == 'RW' && $args['data']['warranty'] == 'R3') {
+            return ['errors' => ['R3 warranty is not allowed for type RW']];
+        }
+
+        $issuingSite = IssuingSiteModel::getById(['id' => $args['data']['issuingSiteId'], 'select' => [1]]);
+        if (empty($issuingSite)) {
+            return ['errors' => ['Issuing site does not exist']];
+        }
+
+        $range = RegisteredNumberRangeModel::get([
+            'select'    => ['id', 'range_end', 'current_number'],
+            'where'     => ['type = ?', 'site_id = ?', 'status = ?'],
+            'data'      => [$args['data']['type'], $args['data']['issuingSiteId'], 'OK']
+        ]);
+        if (empty($range)) {
+            return ['errors' => ['No range found']];
+        }
+
+        $status = $range['current_number'] + 1 > $range['range_end'] ? 'DEL' : 'OK';
+        RegisteredNumberRangeModel::update([
+            'set'   => ['current_number' => $range['current_number'] + 1, 'status' => $status],
+            'where' => ['id = ?'],
+            'data'  => [$range['id']]
+        ]);
+
+        $date = new \DateTime($resource['departure_date']);
+        $date = $date->format('d/m/Y');
+
+        RegisteredMailModel::create([
+            'res_id'        => $args['resId'],
+            'type'          => $args['data']['type'],
+            'issuing_site'  => $args['data']['issuingSiteId'],
+            'warranty'      => $args['data']['warranty'],
+            'letter'        => empty($args['letter']) ? 'false' : 'true',
+            'recipient'     => json_encode($args['data']['recipient']),
+            'number'        => $range['current_number'],
+            'reference'     => "{$date} - {$args['data']['reference']}",
+            'generated'     => 'false',
+        ]);
+
+        return true;
+    }
+
+    public static function saveAndPrintRegisteredMail(array $args)
+    {
+        ValidatorModel::notEmpty($args, ['resId', 'data']);
+        ValidatorModel::intVal($args, ['resId']);
+        ValidatorModel::arrayType($args, ['data']);
+
+        $resource = ResModel::getById(['select' => ['departure_date'], 'resId' => $args['resId']]);
+        if (empty($resource['departure_date'])) {
+            return ['errors' => ['Departure date is empty']];
+        }
+
+        if (!in_array($args['data']['type'], ['2D', '2C', 'RW'])) {
+            return ['errors' => ['Type is not correct']];
+        } elseif (!in_array($args['data']['warranty'], ['R1', 'R2', 'R3'])) {
+            return ['errors' => ['Type is not correct']];
+        } elseif ($args['data']['type'] == 'RW' && $args['data']['warranty'] == 'R3') {
+            return ['errors' => ['R3 warranty is not allowed for type RW']];
+        }
+
+        $issuingSite = IssuingSiteModel::getById(['id' => $args['data']['issuingSiteId'], 'select' => [1]]);
+        if (empty($issuingSite)) {
+            return ['errors' => ['Issuing site does not exist']];
+        }
+
+        $range = RegisteredNumberRangeModel::get([
+            'select'    => ['id', 'range_end', 'current_number'],
+            'where'     => ['type = ?', 'site_id = ?', 'status = ?'],
+            'data'      => [$args['data']['type'], $args['data']['issuingSiteId'], 'OK']
+        ]);
+        if (empty($range)) {
+            return ['errors' => ['No range found']];
+        }
+
+        $status = $range['current_number'] + 1 > $range['range_end'] ? 'DEL' : 'OK';
+        RegisteredNumberRangeModel::update([
+            'set'   => ['current_number' => $range['current_number'] + 1, 'status' => $status],
+            'where' => ['id = ?'],
+            'data'  => [$range['id']]
+        ]);
+
+        $date = new \DateTime($resource['departure_date']);
+        $date = $date->format('d/m/Y');
+
+        RegisteredMailModel::create([
+            'res_id'        => $args['resId'],
+            'type'          => $args['data']['type'],
+            'issuing_site'  => $args['data']['issuingSiteId'],
+            'warranty'      => $args['data']['warranty'],
+            'letter'        => empty($args['letter']) ? 'false' : 'true',
+            'recipient'     => json_encode($args['data']['recipient']),
+            'number'        => $range['current_number'],
+            'reference'     => "{$date} - {$args['data']['reference']}",
+            'generated'     => 'false',
+        ]);
+
+        return true;
+    }
+}
diff --git a/src/app/registeredMail/models/RegisteredMailModel.php b/src/app/registeredMail/models/RegisteredMailModel.php
new file mode 100644
index 0000000000000000000000000000000000000000..9004881cb36ebef1698205c9bc3b2b1338446626
--- /dev/null
+++ b/src/app/registeredMail/models/RegisteredMailModel.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+* Copyright Maarch since 2008 under licence GPLv3.
+* See LICENCE.txt file at the root folder for more details.
+* This file is part of Maarch software.
+*/
+
+/**
+ * @brief Registered Mail Model
+ * @author dev@maarch.org
+ */
+
+namespace RegisteredMail\models;
+
+use SrcCore\models\ValidatorModel;
+use SrcCore\models\DatabaseModel;
+
+class RegisteredMailModel
+{
+    public static function get(array $args = [])
+    {
+        ValidatorModel::arrayType($args, ['select']);
+
+        return DatabaseModel::select([
+            'select'   => empty($args['select']) ? ['*'] : $args['select'],
+            'table'    => ['registered_mail_resources'],
+            'where'    => empty($args['where']) ? [] : $args['where'],
+            'data'     => empty($args['data']) ? [] : $args['data'],
+            'order_by' => empty($args['orderBy']) ? [] : $args['orderBy'],
+            'limit'    => empty($args['limit']) ? 0 : $args['limit']
+        ]);
+    }
+
+    public static function getById(array $args)
+    {
+        ValidatorModel::notEmpty($args, ['id']);
+        ValidatorModel::intVal($args, ['id']);
+        ValidatorModel::arrayType($args, ['select']);
+
+        $item = DatabaseModel::select([
+            'select' => empty($args['select']) ? ['*'] : $args['select'],
+            'table'  => ['registered_mail_resources'],
+            'where'  => ['id = ?'],
+            'data'   => [$args['id']]
+        ]);
+
+        if (empty($item[0])) {
+            return [];
+        }
+
+        return $item[0];
+    }
+
+    public static function create(array $args)
+    {
+        ValidatorModel::notEmpty($args, ['res_id', 'type', 'issuing_site', 'warranty', 'recipient', 'number']);
+
+        DatabaseModel::insert([
+            'table'         => 'registered_mail_resources',
+            'columnsValues' => [
+                'res_id'        => $args['res_id'],
+                'type'          => $args['type'],
+                'issuing_site'  => $args['issuing_site'],
+                'warranty'      => $args['warranty'],
+                'letter'        => $args['letter'] ?? 'false',
+                'recipient'     => $args['recipient'],
+                'number'        => $args['number'],
+                'reference'     => $args['reference'] ?? null,
+                'generated'     => $args['generated'] ?? 'false',
+            ]
+        ]);
+
+        return true;
+    }
+}