diff --git a/src/configuration/config.dev.ts b/src/configuration/config.dev.ts
index 30d4a3f124d3f64e1c4427164e985e193a07535f..90917c6903316ef855ee1e2d21e7b93dcb7aa76a 100644
--- a/src/configuration/config.dev.ts
+++ b/src/configuration/config.dev.ts
@@ -7,31 +7,4 @@ export const configDev = {
   from: 'inclusionnumerique@grandlyon.com',
   from_name: 'Réseau des acteurs de la médiation numérique',
   replyTo: 'inclusionnumerique@grandlyon.com',
-  templates: {
-    directory: './src/mailer/mail-templates',
-    verify: {
-      ejs: 'verify.ejs',
-      json: 'verify.json',
-    },
-    changeEmail: {
-      ejs: 'changeEmail.ejs',
-      json: 'changeEmail.json',
-    },
-    resetPassword: {
-      ejs: 'resetPassword.ejs',
-      json: 'resetPassword.json',
-    },
-    adminStructureClaim: {
-      ejs: 'adminStructureClaim.ejs',
-      json: 'adminStructureClaim.json',
-    },
-    structureClaimValidation: {
-      ejs: 'structureClaimValidation.ejs',
-      json: 'structureClaimValidation.json',
-    },
-    structureOutdatedInfo: {
-      ejs: 'structureOutdatedInfo.ejs',
-      json: 'structureOutdatedInfo.json',
-    },
-  },
 };
diff --git a/src/configuration/config.prod.ts b/src/configuration/config.prod.ts
index 19167f8c124329bc4acf99b1d1ce7c3dc84ed032..d9333dd6499c2498a7853abf5ad97b8e1a2c849c 100644
--- a/src/configuration/config.prod.ts
+++ b/src/configuration/config.prod.ts
@@ -7,31 +7,4 @@ export const configProd = {
   from: 'inclusionnumerique@grandlyon.com',
   from_name: 'Réseau des acteurs de la médiation numérique',
   replyTo: 'inclusionnumerique@grandlyon.com',
-  templates: {
-    directory: './src/mailer/mail-templates',
-    verify: {
-      ejs: 'verify.ejs',
-      json: 'verify.json',
-    },
-    changeEmail: {
-      ejs: 'changeEmail.ejs',
-      json: 'changeEmail.json',
-    },
-    resetPassword: {
-      ejs: 'resetPassword.ejs',
-      json: 'resetPassword.json',
-    },
-    adminStructureClaim: {
-      ejs: 'adminStructureClaim.ejs',
-      json: 'adminStructureClaim.json',
-    },
-    structureClaimValidation: {
-      ejs: 'structureClaimValidation.ejs',
-      json: 'structureClaimValidation.json',
-    },
-    structureOutdatedInfo: {
-      ejs: 'structureOutdatedInfo.ejs',
-      json: 'structureOutdatedInfo.json',
-    },
-  },
 };
diff --git a/src/configuration/config.ts b/src/configuration/config.ts
index eef3f2b02b8461813160d4700f18229a7d517dfe..e9d940409b5b8b927e138bb8b33a9e2ff6935743 100644
--- a/src/configuration/config.ts
+++ b/src/configuration/config.ts
@@ -41,5 +41,9 @@ export const config = {
       ejs: 'tempUserRegistration.ejs',
       json: 'tempUserRegistration.json',
     },
+    structureJoinRequest: {
+      ejs: 'structureJoinRequest.ejs',
+      json: 'structureJoinRequest.json',
+    },
   },
 };
diff --git a/src/configuration/configuration.service.ts b/src/configuration/configuration.service.ts
index 13b4f5e4d827e55bc85ba4cacb6dc54ada5c887c..ca6f9e6c44a78e2259c3b3e70c8a5168d3d30d95 100644
--- a/src/configuration/configuration.service.ts
+++ b/src/configuration/configuration.service.ts
@@ -10,9 +10,11 @@ export class ConfigurationService {
     // Initializing conf with values from var env
     if (process.env.NODE_ENV && process.env.NODE_ENV === 'production') {
       this._config = configProd;
+      this._config.templates = config.templates; // Add mail templates
       Logger.log('App started with production conf', 'ConfigurationService');
     } else if (process.env.NODE_ENV && process.env.NODE_ENV === 'dev') {
       this._config = configDev;
+      this._config.templates = config.templates; // Add mail templates
       Logger.log('App started with dev conf', 'ConfigurationService');
     } else {
       this._config = config;
diff --git a/src/mailer/mail-templates/structureJoinRequest.ejs b/src/mailer/mail-templates/structureJoinRequest.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..550665e04bb48c9f796a21b97fef4dfc11407107
--- /dev/null
+++ b/src/mailer/mail-templates/structureJoinRequest.ejs
@@ -0,0 +1,21 @@
+Bonjour<br />
+<br />
+Vous recevez ce message car <strong><%= surname %></strong> <strong><%= name %></strong> demande a rejoindre votre
+stucture <strong><%= structureName %></strong> sur RES'in, le réseau des acteurs de l'inclusion numérique de la
+Métropole de Lyon. Vous pouvez dès maintenant valider la demande en
+<a
+  href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/join?id=<%= id %>&userId=<%= userId %>&status=true"
+  >cliquant ici</a
+>
+ou refuser la demande
+<a
+  href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/join?id=<%= id %>&userId=<%= userId %>&status=false"
+  >cliquant ici</a
+>.
+<br />
+Cordialement,
+<br />
+L'équipe RES'in
+<br />
+<br />
+Ce mail est un mail automatique. Merci de ne pas y répondre.
diff --git a/src/mailer/mail-templates/structureJoinRequest.json b/src/mailer/mail-templates/structureJoinRequest.json
new file mode 100644
index 0000000000000000000000000000000000000000..8b756e80dda646b74494d044a1c027c48294ac34
--- /dev/null
+++ b/src/mailer/mail-templates/structureJoinRequest.json
@@ -0,0 +1,3 @@
+{
+  "subject": "Un acteur demande a rejoindre votre structure, Réseau des Acteurs de la Médiation Numérique de la Métropole de Lyon"
+}
diff --git a/src/structures/services/structures.service.ts b/src/structures/services/structures.service.ts
index 83c48a25995b9c7ce714c8dce9635e69d4aa3e1a..6a67a5367fbaee90330da8b911caf7501f75819d 100644
--- a/src/structures/services/structures.service.ts
+++ b/src/structures/services/structures.service.ts
@@ -11,6 +11,7 @@ import { User } from '../../users/schemas/user.schema';
 import { MailerService } from '../../mailer/mailer.service';
 import { Cron, CronExpression } from '@nestjs/schedule';
 import { DateTime } from 'luxon';
+import { IUser } from '../../users/interfaces/user.interface';
 
 @Injectable()
 export class StructuresService {
@@ -113,7 +114,7 @@ export class StructuresService {
     return this.findOne(idStructure);
   }
 
-  public async findOne(idParam: string): Promise<Structure> {
+  public async findOne(idParam: string): Promise<StructureDocument> {
     return await this.structureModel.findById(Types.ObjectId(idParam)).exec();
   }
   /**
@@ -284,7 +285,7 @@ export class StructuresService {
   }
 
   /**
-   * Generate activation token and send it to user by email, in order to validate
+   * Send an email to prevent outdated
    * a new account.
    * @param user User
    */
@@ -301,6 +302,34 @@ export class StructuresService {
     this.mailerService.send(userEmail, jsonConfig.subject, html);
   }
 
+  /**
+   * Send an email to structure owner's in order to accept or decline a join request
+   * @param user User
+   */
+  public async sendStructureJoinRequest(user: IUser, structure: StructureDocument): Promise<any> {
+    const config = this.mailerService.config;
+    const ejsPath = this.mailerService.getTemplateLocation(config.templates.structureJoinRequest.ejs);
+    const jsonConfig = this.mailerService.loadJsonConfig(config.templates.structureJoinRequest.json);
+
+    const html = await ejs.renderFile(ejsPath, {
+      config,
+      structureName: structure.structureName,
+      name: user.name,
+      surname: user.surname,
+      id: structure._id,
+      userId: user._id,
+    });
+    const owners = await this.getOwners(structure._id);
+    owners.forEach((owner) => {
+      this.mailerService.send(owner.email, jsonConfig.subject, html);
+    });
+  }
+
+  private async getOwners(structureId: string): Promise<IUser[]> {
+    // Get owners of outdated structures
+    return this.userService.getStructureOwners(structureId);
+  }
+
   public async updateAccountVerified(idStructure: string, emailUser: string): Promise<Structure> {
     const user = await this.userService.findOne(emailUser);
     const structureLinked = await this.findOne(user.structuresLink[0].toHexString());
diff --git a/src/structures/structures.controller.ts b/src/structures/structures.controller.ts
index 2067fb53460239d16910d99f9bd3b853dcd1dd3a..4a58d7162c63babafe0766b27559af953a335def 100644
--- a/src/structures/structures.controller.ts
+++ b/src/structures/structures.controller.ts
@@ -1,4 +1,17 @@
-import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query, UseGuards } from '@nestjs/common';
+import {
+  Body,
+  Controller,
+  Delete,
+  Get,
+  HttpException,
+  HttpStatus,
+  Param,
+  ParseIntPipe,
+  Post,
+  Put,
+  Query,
+  UseGuards,
+} from '@nestjs/common';
 import { ApiParam } from '@nestjs/swagger';
 import { Types } from 'mongoose';
 import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
@@ -107,9 +120,12 @@ export class StructuresController {
   @UseGuards(JwtAuthGuard, IsStructureOwnerGuard)
   @Roles('admin')
   @ApiParam({ name: 'id', type: String, required: true })
-  public async addOwner(@Param('id') id: string, @Body() user: CreateTempUserDto) {
+  public async addOwner(@Param('id') id: string, @Body() user: CreateTempUserDto): Promise<any> {
     // Get structure name
     const structure = await this.structureService.findOne(id);
+    if (!structure) {
+      throw new HttpException('Invalid Structure', HttpStatus.NOT_FOUND);
+    }
     user.pendingStructuresLink = [Types.ObjectId(id)];
     // If user already exist, use created account
     if (await this.userService.verifyUserExist(user.email)) {
@@ -123,11 +139,79 @@ export class StructuresController {
     return this.tempUserService.create(user, structure.structureName);
   }
 
+  @Post(':id/join')
+  @ApiParam({ name: 'id', type: String, required: true })
+  public async join(@Param('id') id: string, @Body() user: User): Promise<any> {
+    // Get structure name
+    const structure = await this.structureService.findOne(id);
+    if (!structure) {
+      throw new HttpException('Invalid Structure', HttpStatus.NOT_FOUND);
+    }
+    // Get user and add pending structure
+    const userFromDb = await this.userService.findOne(user.email);
+    if (!userFromDb) {
+      throw new HttpException('Invalid User', HttpStatus.NOT_FOUND);
+    }
+    // If user has not already request it, send owner's validation email
+    if (!userFromDb.pendingStructuresLink.includes(Types.ObjectId(id))) {
+      userFromDb.pendingStructuresLink.push(Types.ObjectId(id));
+      userFromDb.save();
+      // Send structure owner's an email
+      this.structureService.sendStructureJoinRequest(userFromDb, structure);
+    }
+  }
+
+  @Post(':id/join/:userId/:status')
+  @UseGuards(JwtAuthGuard, IsStructureOwnerGuard)
+  @ApiParam({ name: 'id', type: String, required: true })
+  @ApiParam({ name: 'userId', type: String, required: true })
+  @ApiParam({ name: 'status', type: String, required: true })
+  public async joinValidation(
+    @Param('id') id: string,
+    @Param('status') status: string,
+    @Param('userId') userId: string
+  ): Promise<any> {
+    // Get structure name
+    const structure = await this.structureService.findOne(id);
+    if (!structure) {
+      throw new HttpException('Invalid Structure', HttpStatus.NOT_FOUND);
+    }
+
+    // Get user and add pending structure
+    const userFromDb = await this.userService.findById(userId);
+    if (!userFromDb) {
+      throw new HttpException('Invalid User', HttpStatus.NOT_FOUND);
+    }
+
+    if (!userFromDb.pendingStructuresLink.includes(Types.ObjectId(id))) {
+      throw new HttpException('User not linked to structure', HttpStatus.NOT_FOUND);
+    }
+
+    if (status === 'true') {
+      // Accept
+      await this.userService.updateStructureLinked(userFromDb.email, id);
+      await this.userService.removeFromPendingStructureLinked(userFromDb.email, id);
+    } else {
+      // Refuse
+      this.userService.removeFromPendingStructureLinked(userFromDb.email, id);
+    }
+  }
+
   @Delete(':id/removeOwner')
   @UseGuards(JwtAuthGuard, IsStructureOwnerGuard)
   @Roles('admin')
   @ApiParam({ name: 'id', type: String, required: true })
-  public async removeOwner(@Param('id') id: string, @Body() user: User) {
-    //TODO: remove stucture from user structure list
+  public async removeOwner(@Param('id') id: string, @Body() user: User): Promise<any> {
+    // Get structure
+    const structure = await this.structureService.findOne(id);
+    if (!structure) {
+      throw new HttpException('Invalid Structure', HttpStatus.NOT_FOUND);
+    }
+    // Get user
+    const userFromDb = await this.userService.findOne(user.email);
+    if (!userFromDb) {
+      throw new HttpException('Invalid User', HttpStatus.NOT_FOUND);
+    }
+    this.userService.removeFromStructureLinked(userFromDb.email, id);
   }
 }
diff --git a/src/users/users.service.ts b/src/users/users.service.ts
index 0763ff83d7822a9741928f7d2b03a1ef9aa85540..49aa23df94f0a63cf103c2c4a9466f93b3505d71 100644
--- a/src/users/users.service.ts
+++ b/src/users/users.service.ts
@@ -312,6 +312,10 @@ export class UsersService {
     return this.userModel.findOne({ structuresLink: Types.ObjectId(structureId) }).exec();
   }
 
+  public getStructureOwners(structureId: string): Promise<IUser[]> {
+    return this.userModel.find({ structuresLink: Types.ObjectId(structureId) }).exec();
+  }
+
   public async isUserAlreadyClaimedStructure(structureId: string, userEmail: string): Promise<boolean> {
     const user = await this.findOne(userEmail, true);
     if (user) {
@@ -331,7 +335,7 @@ export class UsersService {
     if (user) {
       if (!user.pendingStructuresLink.includes(Types.ObjectId(idStructure))) {
         user.pendingStructuresLink.push(Types.ObjectId(idStructure));
-        user.save();
+        await user.save();
         return user.pendingStructuresLink;
       }
       throw new HttpException('User already claimed this structure', HttpStatus.NOT_FOUND);
@@ -339,15 +343,42 @@ export class UsersService {
     throw new HttpException('Invalid user', HttpStatus.NOT_FOUND);
   }
 
+  public async removeFromPendingStructureLinked(userEmail: string, idStructure: string): Promise<Types.ObjectId[]> {
+    const user = await this.findOne(userEmail, true);
+    if (user) {
+      if (user.pendingStructuresLink.includes(Types.ObjectId(idStructure))) {
+        user.pendingStructuresLink = user.pendingStructuresLink.filter((structureId) => {
+          return structureId === Types.ObjectId(idStructure);
+        });
+        await user.save();
+        return user.pendingStructuresLink;
+      }
+      throw new HttpException('User already belong to this structure', HttpStatus.NOT_FOUND);
+    }
+    throw new HttpException('Invalid user', HttpStatus.NOT_FOUND);
+  }
+
   public async updateStructureLinked(userEmail: string, idStructure: string): Promise<Types.ObjectId[]> {
     const user = await this.findOne(userEmail, true);
     if (user) {
-      if (
-        !user.pendingStructuresLink.includes(Types.ObjectId(idStructure)) &&
-        !user.structuresLink.includes(Types.ObjectId(idStructure))
-      ) {
+      if (!user.structuresLink.includes(Types.ObjectId(idStructure))) {
         user.structuresLink.push(Types.ObjectId(idStructure));
-        user.save();
+        await user.save();
+        return user.structuresLink;
+      }
+      throw new HttpException('User already belong to this structure', HttpStatus.NOT_FOUND);
+    }
+    throw new HttpException('Invalid user', HttpStatus.NOT_FOUND);
+  }
+
+  public async removeFromStructureLinked(userEmail: string, idStructure: string): Promise<Types.ObjectId[]> {
+    const user = await this.findOne(userEmail, true);
+    if (user) {
+      if (user.structuresLink.includes(Types.ObjectId(idStructure))) {
+        user.structuresLink = user.structuresLink.filter((structureId) => {
+          return structureId == Types.ObjectId(idStructure);
+        });
+        await user.save();
         return user.structuresLink;
       }
       throw new HttpException('User already belong to this structure', HttpStatus.NOT_FOUND);