diff --git a/src/admin/admin.controller.spec.ts b/src/admin/admin.controller.spec.ts
index 056f80ed46a68a678ffd27e8c8c1de2813e66fc7..525d002151ff9ebc80e45f4eecf0c4923217f9fb 100644
--- a/src/admin/admin.controller.spec.ts
+++ b/src/admin/admin.controller.spec.ts
@@ -77,7 +77,6 @@ describe('AdminController', () => {
 
   const mockAdminService = {
     isDateOutdated: jest.fn(),
-    getLastUpdateDate: jest.fn(),
   };
 
   const pendingStructureTest: PendingStructureDto = {
@@ -158,7 +157,7 @@ describe('AdminController', () => {
   it('should get pending attachments', async () => {
     mockStructureService.findOne.mockResolvedValue({ structureName: 'structure', updatedAt: '' });
     expect((await adminController.getPendingAttachments()).length).toBe(2);
-    expect(Object.keys((await adminController.getPendingAttachments())[0]).length).toBe(5);
+    expect(Object.keys((await adminController.getPendingAttachments())[0]).length).toBe(6);
   });
 
   describe('Pending structures validation', () => {
@@ -362,7 +361,6 @@ describe('AdminController', () => {
 
   it('should get pending structure list for admin', async () => {
     mockAdminService.isDateOutdated.mockReturnValue(false);
-    mockAdminService.getLastUpdateDate.mockReturnValue('');
     mockStructureService.findOne.mockResolvedValue({ structureName: 'structure' });
     // using _id for unclaimed
     mockStructureService.findAllUnclaimed.mockResolvedValueOnce([
diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts
index 760c2b0ffa9d93a7257c322683afa23d06d80e8f..e9724bd236908ffe9b92003bb9b7154b6a784e33 100644
--- a/src/admin/admin.controller.ts
+++ b/src/admin/admin.controller.ts
@@ -16,7 +16,6 @@ import { validate } from 'class-validator';
 import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
 import { EspaceCoopCNFS } from '../espaceCoop/schemas/espaceCoopCNFS.schema';
 import { EspaceCoopService } from '../espaceCoop/services/espaceCoop.service';
-import { NewsletterService } from '../newsletter/newsletter.service';
 import { Structure, StructureDocument } from '../structures/schemas/structure.schema';
 import { StructuresService } from '../structures/services/structures.service';
 import { Roles } from '../users/decorators/roles.decorator';
@@ -30,6 +29,15 @@ import { PendingStructureDto } from './dto/pending-structure.dto';
 import { SetUserEmployerDto } from './dto/set-user-employer.dto';
 import { SetUserJobDto } from './dto/set-user-job.dto';
 
+type PendingStructure = {
+  userEmail: string;
+  structureId: string;
+  createdAt: string;
+  structureName: string;
+  updatedAt: string;
+  permalink: string;
+};
+
 @ApiTags('admin')
 @Controller('admin')
 export class AdminController {
@@ -39,7 +47,6 @@ export class AdminController {
     private structuresService: StructuresService,
     private jobsService: JobsService,
     private employerService: EmployerService,
-    private newsletterService: NewsletterService,
     private adminService: AdminService,
     private espaceCoopCNFSService: EspaceCoopService
   ) {}
@@ -48,13 +55,14 @@ export class AdminController {
   @Roles('admin')
   @Get('pendingStructures')
   @ApiOperation({ description: 'Get pending structures for validation' })
-  public async getPendingAttachments(): Promise<PendingStructureDto[]> {
+  public async getPendingAttachments(): Promise<PendingStructure[]> {
     const pendingStructure = await this.usersService.getPendingStructures();
     return Promise.all(
       pendingStructure.map(async (structure) => {
         const structureDocument = await this.structuresService.findOne(structure.structureId);
         structure.structureName = structureDocument.structureName;
         structure.updatedAt = structureDocument.updatedAt;
+        structure.permalink = structureDocument.permalink;
         return structure;
       })
     );
@@ -68,22 +76,26 @@ export class AdminController {
     this.logger.debug('getAdminStructuresList');
     const structuresList = { claimed: [], inClaim: [], toClaim: [], incomplete: [] };
     const inClaimStructures = await this.getPendingAttachments();
-    structuresList.inClaim = inClaimStructures.map((structure) => {
-      const lastUpdateDate = this.adminService.getLastUpdateDate(structure);
-      return {
-        structureId: structure.structureId,
-        structureName: structure.structureName,
-        updatedAt: lastUpdateDate,
-        isOutdated: this.adminService.isDateOutdated(lastUpdateDate, 6),
-      };
-    });
+    structuresList.inClaim = await Promise.all(
+      inClaimStructures.map(async (structure) => {
+        const lastUpdateDate = structure.updatedAt ? structure.updatedAt : structure.createdAt;
+        return {
+          structureId: structure.structureId,
+          permalink: structure.permalink,
+          structureName: structure.structureName,
+          updatedAt: structure.updatedAt ? structure.updatedAt : structure.createdAt,
+          isOutdated: this.adminService.isDateOutdated(lastUpdateDate, 6),
+        };
+      })
+    );
     const toClaimStructures = await this.structuresService.findAllUnclaimed();
     structuresList.toClaim = toClaimStructures
       .filter((demand) => !structuresList.inClaim.find((elem) => elem.structureId == demand.id))
       .map((structure) => {
-        const lastUpdateDate = this.adminService.getLastUpdateDate(structure);
+        const lastUpdateDate = structure.updatedAt ? structure.updatedAt : structure.createdAt;
         return {
           structureId: structure._id,
+          permalink: structure.permalink,
           structureName: structure.structureName,
           updatedAt: lastUpdateDate,
           isOutdated: this.adminService.isDateOutdated(lastUpdateDate, 6),
@@ -97,9 +109,10 @@ export class AdminController {
           !structuresList.toClaim.find((elem) => elem.structureId == demand.id)
       )
       .map((structure) => {
-        const lastUpdateDate = this.adminService.getLastUpdateDate(structure);
+        const lastUpdateDate = structure.updatedAt ? structure.updatedAt : structure.createdAt;
         return {
           structureId: structure.id,
+          permalink: structure.permalink,
           structureName: structure.structureName,
           updatedAt: lastUpdateDate,
           isOutdated: this.adminService.isDateOutdated(lastUpdateDate, 6),
@@ -110,9 +123,10 @@ export class AdminController {
         const validity = await validate(new Structure(struct));
         if (validity.length > 0) {
           this.logger.debug(`getAdminStructuresList - validation failed. errors: ${validity.toString()}`);
-          const lastUpdateDate = this.adminService.getLastUpdateDate(struct);
+          const lastUpdateDate = struct.updatedAt ? struct.updatedAt : struct.createdAt;
           return {
             structureId: struct.id,
+            permalink: struct.permalink,
             structureName: struct.structureName,
             updatedAt: lastUpdateDate,
             isOutdated: this.adminService.isDateOutdated(lastUpdateDate, 6),
diff --git a/src/admin/admin.service.ts b/src/admin/admin.service.ts
index 62ea4ad0473e2f679d99ffb9de32d66d3fcb7f7a..4169f47ede800dc511fed227de6a0256adf7e236 100644
--- a/src/admin/admin.service.ts
+++ b/src/admin/admin.service.ts
@@ -1,8 +1,5 @@
 import { Injectable } from '@nestjs/common';
 import { DateTime, Interval } from 'luxon';
-import { Structure } from '../structures/schemas/structure.schema';
-import { PendingStructureDto } from './dto/pending-structure.dto';
-import { UnclaimedStructureDto } from './dto/unclaimed-structure-dto';
 
 @Injectable()
 export class AdminService {
@@ -10,7 +7,4 @@ export class AdminService {
     const today = DateTime.local().setZone('utc', { keepLocalTime: true });
     return Interval.fromDateTimes(date, today).length('months') > nbMonths;
   }
-  public getLastUpdateDate(structure: Structure | UnclaimedStructureDto | PendingStructureDto): DateTime {
-    return structure.updatedAt ? structure.updatedAt : structure.createdAt;
-  }
 }
diff --git a/src/auth/auth.controller.spec.ts b/src/auth/auth.controller.spec.ts
index 27a4f2fa5a6be0719141a7080bed6b7add616f7f..ce44fe09d9736b2b9d5c9341f27ceedd3b6ba3fc 100644
--- a/src/auth/auth.controller.spec.ts
+++ b/src/auth/auth.controller.spec.ts
@@ -4,10 +4,12 @@ import { getModelToken } from '@nestjs/mongoose';
 import { PassportModule } from '@nestjs/passport';
 import { Test } from '@nestjs/testing';
 import { AuthServiceMock } from '../../test/mock/services/auth.mock.service';
+import { NewsletterServiceMock } from '../../test/mock/services/newsletter.mock.service';
 import { StructuresServiceMock } from '../../test/mock/services/structures.mock.service';
 import { CategoriesService } from '../categories/services/categories.service';
 import { ConfigurationModule } from '../configuration/configuration.module';
 import { MailerModule } from '../mailer/mailer.module';
+import { NewsletterService } from '../newsletter/newsletter.service';
 import { StructuresSearchService } from '../structures/services/structures-search.service';
 import { StructuresService } from '../structures/services/structures.service';
 import { Job } from '../users/schemas/job.schema';
@@ -18,8 +20,6 @@ import { UsersService } from '../users/services/users.service';
 import { AuthController } from './auth.controller';
 import { AuthService } from './auth.service';
 import { LoginDto } from './login-dto';
-import { NewsletterService } from '../newsletter/newsletter.service';
-import { NewsletterServiceMock } from '../../test/mock/services/newsletter.mock.service';
 
 describe('AuthController', () => {
   let authController: AuthController;
@@ -34,6 +34,8 @@ describe('AuthController', () => {
     get: jest.fn(),
   };
 
+  const mockJobsService = {};
+
   beforeEach(async () => {
     const module = await Test.createTestingModule({
       imports: [
@@ -86,6 +88,10 @@ describe('AuthController', () => {
           provide: NewsletterService,
           useClass: NewsletterServiceMock,
         },
+        {
+          provide: JobsService,
+          useValue: mockJobsService,
+        },
       ],
     }).compile();
 
diff --git a/src/mailer/mail-templates/adminStructureCreate.ejs b/src/mailer/mail-templates/adminStructureCreate.ejs
index e86a52d2f1ffc688b8232a347cfe8bc0254ae2d6..1bbdd5967271085d34bfad0ca2b05941639d8692 100644
--- a/src/mailer/mail-templates/adminStructureCreate.ejs
+++ b/src/mailer/mail-templates/adminStructureCreate.ejs
@@ -1,6 +1,7 @@
 Bonjour,<br />
 <br />
 Une nouvelle structure a été créée:
-<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/acteurs?id=<%= id %>"
+<a
+  href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/acteurs?structure=<%= permalink %>"
   ><strong><%= structureName %></strong></a
 >
diff --git a/src/mailer/mail-templates/adminUserCreate.ejs b/src/mailer/mail-templates/adminUserCreate.ejs
index c64fa949200363953c2df71a900961297f657195..d9a57e54874c3b9cf1377230252f9c22e9b2a66f 100644
--- a/src/mailer/mail-templates/adminUserCreate.ejs
+++ b/src/mailer/mail-templates/adminUserCreate.ejs
@@ -2,7 +2,9 @@ Bonjour,<br />
 <br />
 Un nouvel utilisateur a été créé (<strong><%= name %> <%= surname %></strong>)
 <br />
-<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/profile/<%= userId %>">
+<a
+  href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/profil/<%= userPermalink %>"
+>
   Accédez à son profil
 </a>
 <br />
diff --git a/src/mailer/mail-templates/resetPassword.ejs b/src/mailer/mail-templates/resetPassword.ejs
index 233966c0e062c056cb73c5afcbdd2461448c941b..389a748ee0878cac4e0bfb9d22b1229190f5b360 100644
--- a/src/mailer/mail-templates/resetPassword.ejs
+++ b/src/mailer/mail-templates/resetPassword.ejs
@@ -4,7 +4,7 @@ Vous avez demandé une réinitialisation de votre mot de passe pour le
 <em>Réseau des Acteurs de la Médiation Numérique de la Métropole de Lyon</em>. Pour changer de mot de passe, merci de
 cliquer sur le lien suivant :
 <a
-  href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/reset-password?token=<%= token %>"
+  href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/mot-de-passe-oublie?token=<%= token %>"
   >ce lien</a
 ><br />
 Si vous n'avez pas demandé de réinitiallisation de votre mot de passe, merci d'ignorer cet email.
diff --git a/src/mailer/mail-templates/structureErrorReport.ejs b/src/mailer/mail-templates/structureErrorReport.ejs
index 361e823023251a76fd9d3fe584b6ed9ce0d623ef..52b8e35453adf00cc1e1f7d1adbaba970ed86835 100644
--- a/src/mailer/mail-templates/structureErrorReport.ejs
+++ b/src/mailer/mail-templates/structureErrorReport.ejs
@@ -6,6 +6,7 @@ Voici le message:<br />
 <br />
 <strong><%= content %></strong><br />
 <br />
-<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/acteurs?id=<%= id %>"
+<a
+  href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/acteurs?structure=<%= permalink %>"
   >Accéder à votre structure</a
 >.
diff --git a/src/mailer/mail-templates/structureModificationNotification.ejs b/src/mailer/mail-templates/structureModificationNotification.ejs
index f4153b552a2ee8587a713e7a4a79201bfc351a2b..50fda658928c5667fa1f560d45c2926202f9077e 100644
--- a/src/mailer/mail-templates/structureModificationNotification.ejs
+++ b/src/mailer/mail-templates/structureModificationNotification.ejs
@@ -2,6 +2,7 @@ Bonjour,<br />
 <br />
 Un utilisateur a modifié une ou plusieurs informations sur la fiche de sa structure (<%= structureName %>).
 <br />
-<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/acteurs?id=<%= id %>"
+<a
+  href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/acteurs?structure=<%= permalink %>"
   >Accéder à cette structure</a
 >.
diff --git a/src/mailer/mail-templates/structureOutdatedInfo.ejs b/src/mailer/mail-templates/structureOutdatedInfo.ejs
index 72e04b9f8e45c191faf2c2a574421179577a1f02..7b8b94e48710ea2d2a12643c8912350d13409cf8 100644
--- a/src/mailer/mail-templates/structureOutdatedInfo.ejs
+++ b/src/mailer/mail-templates/structureOutdatedInfo.ejs
@@ -4,6 +4,6 @@ Vous recevez ce message car votre structure <strong><%= name %></strong> est ré
 acteurs de l'inclusion numérique de la Métropole de Lyon. Pouvez-vous nous aider en vérifiant que vos données sont bien
 à jour en
 <a
-  href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/profile/edit-structure/<%= id %>"
+  href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/profil/edition-structure/<%= id %>"
   >cliquant ici</a
 >.
diff --git a/src/mailer/mail-templates/structureToBeDeleted.ejs b/src/mailer/mail-templates/structureToBeDeleted.ejs
index 05e4659acb9cd5def4180fd568a5a27880bc77f9..d60782a27a2840c85a7173aefb840146b2ef4143 100644
--- a/src/mailer/mail-templates/structureToBeDeleted.ejs
+++ b/src/mailer/mail-templates/structureToBeDeleted.ejs
@@ -9,7 +9,7 @@ suppression en cliquant sur le lien ci-dessous.
 
 <div style="text-align: center">
   <a
-    href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/profile/structures-management"
+    href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/profil/gestion-structures"
     >Gérer mes structures</a
   >
 </div>
diff --git a/src/mailer/mail-templates/tempUserRegistration.ejs b/src/mailer/mail-templates/tempUserRegistration.ejs
index 0b318c49926d53013ab226c4d0cdb5c61c9d16a9..641a7107e8133576393e884ad52005a0a2a47d6b 100644
--- a/src/mailer/mail-templates/tempUserRegistration.ejs
+++ b/src/mailer/mail-templates/tempUserRegistration.ejs
@@ -3,6 +3,7 @@ Bonjour,<br />
 Vous recevez ce message car vous avez été relié à la stucture <strong><%= name %></strong> sur RES'in, le réseau des
 acteurs de l'inclusion numérique de la Métropole de Lyon. Vous pouvez dès maitenant vous créer un compte sur la
 plateforme pour accéder à votre structure en
-<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/form/register/<%= id %>"
+<a
+  href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/formulaire/inscription/<%= id %>"
   >cliquant ici</a
 >.
diff --git a/src/mailer/mail-templates/userAddedToStructure.ejs b/src/mailer/mail-templates/userAddedToStructure.ejs
index 03aa6b961add1fb36074a7a3a878ec161d16a8a4..b3394b36c8f5d6c27f1a7eff38b33d96215b275f 100644
--- a/src/mailer/mail-templates/userAddedToStructure.ejs
+++ b/src/mailer/mail-templates/userAddedToStructure.ejs
@@ -2,5 +2,5 @@ Bonjour,<br />
 <br />
 Vous recevez ce message car vous avez été relié à la stucture <strong><%= name %></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 la consulter sur
-<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/profile">votre profil</a
+<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/profil">votre profil</a
 >.
diff --git a/src/migrations/scripts/1713961381613-add-user-and-structure-permalink.ts b/src/migrations/scripts/1713961381613-add-user-and-structure-permalink.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0d914d7ee7489076d520bb6f05196b7d78e84c9e
--- /dev/null
+++ b/src/migrations/scripts/1713961381613-add-user-and-structure-permalink.ts
@@ -0,0 +1,50 @@
+import { Db } from 'mongodb';
+import { sanitize } from '../../shared/utils';
+import { getDb } from '../migrations-utils/db';
+
+export const up = async () => {
+  const db: Db = await getDb();
+
+  const usersCursor = db.collection('users').find({});
+  let document;
+  while ((document = await usersCursor.next())) {
+    let permalink = `${sanitize(document.name)}-${sanitize(document.surname)}`;
+
+    // If already exists, add a number to the end of the permalink
+    const exists = await db.collection('users').findOne({ permalink });
+    if (exists) {
+      let count = 1;
+      while (await db.collection('users').findOne({ permalink: `${permalink}-${count}` })) {
+        count++;
+      }
+      permalink = `${permalink}-${count}`;
+    }
+    await db.collection('users').updateOne({ _id: document._id }, [{ $set: { permalink } }]);
+  }
+
+  const structuresCursor = db.collection('structures').find({});
+  while ((document = await structuresCursor.next())) {
+    let permalink = sanitize(document.structureName);
+
+    // If already exists, add a number to the end of the permalink
+    const exists = await db.collection('structures').findOne({ permalink });
+    if (exists) {
+      let count = 1;
+      while (await db.collection('structures').findOne({ permalink: `${permalink}-${count}` })) {
+        count++;
+      }
+      permalink = `${permalink}-${count}`;
+    }
+    await db.collection('structures').updateOne({ _id: document._id }, [{ $set: { permalink } }]);
+  }
+  console.log(`Update done : added permalink to users and structures`);
+};
+
+export const down = async () => {
+  const db: Db = await getDb();
+
+  await db.collection('users').updateMany({}, [{ $unset: 'permalink' }]);
+  await db.collection('structures').updateMany({}, [{ $unset: 'permalink' }]);
+
+  console.log(`Downgrade done : removed permalink from users and structures`);
+};
diff --git a/src/posts/posts.controller.spec.ts b/src/posts/posts.controller.spec.ts
index 5738c687845e13658691414a283b49b258b86837..72d7f927c4d76a0adb42e5e43f9ef3d02b5cb850 100644
--- a/src/posts/posts.controller.spec.ts
+++ b/src/posts/posts.controller.spec.ts
@@ -356,70 +356,4 @@ describe('PostsController', () => {
       expect(result.public.length).toBe(2);
     });
   });
-
-  describe('getPostbyId', () => {
-    it('should get post Hello by id 61c4847b0ff4550001505090', async () => {
-      const data = [
-        {
-          id: '61c4847b0ff4550001505090',
-          uuid: 'f4ee5a37-a343-4cad-8a32-3f6cf87f9569',
-          title: 'Hello',
-          slug: 'hello',
-          html: '<p>Test</p>',
-          comment_id: '61c4847b0ff4550001505090',
-          feature_image: 'http://localhost:2368/content/images/2021/12/dacc-4.png',
-          featured: false,
-          visibility: 'public',
-          email_recipient_filter: 'none',
-          created_at: '2021-12-23T14:15:23.000+00:00',
-          updated_at: '2021-12-23T14:15:45.000+00:00',
-          published_at: '2021-12-23T14:15:45.000+00:00',
-          custom_excerpt: null,
-          codeinjection_head: null,
-          codeinjection_foot: null,
-          custom_template: null,
-          canonical_url: null,
-          tags: [Array],
-          authors: [Array],
-          primary_author: [Object],
-          primary_tag: [Object],
-          url: 'http://localhost:2368/hello/',
-          excerpt:
-            '« Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus.\n' +
-            'Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed,\n' +
-            'dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper\n' +
-            'congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est\n' +
-            'eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu\n' +
-            'massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut\n' +
-            'in risus volutpat libero pharetra tem',
-          reading_time: 2,
-          access: true,
-          send_email_when_published: false,
-          og_image: null,
-          og_title: null,
-          og_description: null,
-          twitter_image: null,
-          twitter_title: null,
-          twitter_description: null,
-          meta_title: null,
-          meta_description: null,
-          email_subject: null,
-          frontmatter: null,
-        },
-      ];
-      const axiosResult: AxiosResponse = {
-        data: {
-          posts: data,
-        },
-        status: 200,
-        statusText: 'OK',
-        headers: {},
-        config: {},
-      };
-      httpServiceMock.get.mockImplementationOnce(() => of(axiosResult));
-      postServiceMock.formatPosts.mockImplementationOnce(() => data);
-      const result = await (await postsController.getPostbyId('61c4847b0ff4550001505090')).toPromise();
-      expect(result).toStrictEqual({ posts: [data] });
-    });
-  });
 });
diff --git a/src/posts/posts.controller.ts b/src/posts/posts.controller.ts
index f11f49a8cdb279c45cd1c1292848fcd786b7e03a..008700f8b1ea4751fa6b73ad962d8393be09784e 100644
--- a/src/posts/posts.controller.ts
+++ b/src/posts/posts.controller.ts
@@ -1,12 +1,12 @@
+import { HttpService } from '@nestjs/axios';
 import { Controller, Get, HttpException, HttpStatus, Logger, Param, Query } from '@nestjs/common';
+import { ApiQuery, ApiTags } from '@nestjs/swagger';
 import { Observable } from 'rxjs';
 import { catchError, map } from 'rxjs/operators';
-import { ApiQuery, ApiTags } from '@nestjs/swagger';
-import { Post } from './schemas/post.schema';
 import { PostsService } from './posts.service';
-import { Tag } from './schemas/tag.schema';
+import { Post } from './schemas/post.schema';
 import { PostWithMeta } from './schemas/postWithMeta.schema';
-import { HttpService } from '@nestjs/axios';
+import { Tag } from './schemas/tag.schema';
 
 @ApiTags('posts')
 @Controller('posts')
@@ -64,10 +64,10 @@ export class PostsController {
     });
   }
 
-  @Get(':id')
-  public async getPostbyId(@Param('id') id: string): Promise<Observable<{ posts: Post[] }>> {
+  @Get(':slug')
+  public async getPostBySlug(@Param('slug') slug: string): Promise<Observable<{ posts: Post[] }>> {
     return this.httpService
-      .get(`${process.env.GHOST_HOST_AND_PORT}/ghost/api/v3/content/posts/` + id, {
+      .get(`${process.env.GHOST_HOST_AND_PORT}/ghost/api/v3/content/posts/slug/` + slug, {
         params: {
           key: process.env.GHOST_CONTENT_API_KEY,
           include: 'tags,authors',
diff --git a/src/shared/utils.spec.ts b/src/shared/utils.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6cc3526af4f35dfe278734bc73bdb29219f6e14b
--- /dev/null
+++ b/src/shared/utils.spec.ts
@@ -0,0 +1,41 @@
+import { sanitize } from './utils';
+
+describe('sanitize', () => {
+  it('should do nothing if the string is already sanitized', () => {
+    expect(sanitize('abcdefghijklmnopqrstuvwxyz1234567890')).toBe('abcdefghijklmnopqrstuvwxyz1234567890');
+  });
+
+  it('should remove trailing spaces', () => {
+    expect(sanitize(' abc ')).toBe('abc');
+  });
+
+  it('should be lowercase', () => {
+    expect(sanitize('ABC')).toBe('abc');
+  });
+
+  it('should remove accents', () => {
+    expect(sanitize('àèìòùÀÈÌÒÙáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇÅå')).toBe(
+      'aeiouaeiouaeiouyaeiouyaeiouaeiouanoanoaeiouyaeiouyccaa'
+    );
+  });
+
+  it('should replace " - " by "-"', () => {
+    expect(sanitize('Mairie - Lyon 8')).toBe('mairie-lyon-8');
+  });
+
+  it('should replace " / " by "-"', () => {
+    expect(sanitize('Mairie / Lyon 8')).toBe('mairie-lyon-8');
+  });
+
+  it('should replace spaces by "-"', () => {
+    expect(sanitize('a b c d')).toBe('a-b-c-d');
+  });
+
+  it('should remove parenthesis', () => {
+    expect(sanitize('Mairie (Lyon 8)')).toBe('mairie-lyon-8');
+  });
+
+  it('should remove all non-alphanumeric characters', () => {
+    expect(sanitize('@%#$.!?;:+=[]{}~\'"')).toBe('');
+  });
+});
diff --git a/src/shared/utils.ts b/src/shared/utils.ts
index 9235c3513c24c12ec5b801ff7ccec9c47f6220ba..c3b78793be8b363b9606ebde330cc68df73e836e 100644
--- a/src/shared/utils.ts
+++ b/src/shared/utils.ts
@@ -63,3 +63,20 @@ export const es_settings_homemade_french: IndicesIndexSettings = {
 export function escapeElasticsearchQuery(query: string): string {
   return query.replace(/(\+|\-|\=|&&|\|\||\>|\<|\!|\(|\)|\{|\}|\[|\]|\^|"|~|\?|\:|\\|\/)/g, '\\$&');
 }
+
+/**
+ *  Remove accents and trailing spaces from a string, replace non-alphanumeric characters with dashes and return the result in lowercase
+ * @example
+ * sanitize(' Métropole de Lyon ') => 'metropole-de-lyon'
+ */
+export const sanitize = (str: string) => {
+  return str
+    .trim()
+    .toLowerCase()
+    .normalize('NFD')
+    .replace(/[\u0300-\u036f]/g, '') // remove accents
+    .replace(/\s-\s/g, '-') // replace " - " by "-"
+    .replace(/\s\/\s/g, '-') // replace " / " by "-"
+    .replace(/\s/g, '-') // replace spaces by "-"
+    .replace(/[^\w\s-]/gi, ''); // remove all non alphanumeric characters except spaces and dashes
+};
diff --git a/src/structures/schemas/structure.schema.ts b/src/structures/schemas/structure.schema.ts
index 37c5b34ebe6507f9f86473c721a0d92c2d9b256a..c55c3c2ba5e3253d5a81a8b7e393a95df6cb94cc 100644
--- a/src/structures/schemas/structure.schema.ts
+++ b/src/structures/schemas/structure.schema.ts
@@ -42,6 +42,7 @@ export class Structure {
     this.idDataGouv = data.idDataGouv;
     this.hasNoUserDN = data.hasNoUserDN;
     this.hasUserWithAppointmentDN = data.hasUserWithAppointmentDN;
+    this.permalink = data.permalink;
   }
 
   @Prop()
@@ -150,6 +151,9 @@ export class Structure {
 
   // No @Prop decorator because this property must not be stored in database
   categoriesWithPersonalOffers: StructureCategories;
+
+  @Prop()
+  permalink: string;
 }
 
 export const StructureSchema = SchemaFactory.createForClass(Structure);
diff --git a/src/structures/services/structures.service.spec.ts b/src/structures/services/structures.service.spec.ts
index 1e055739441c0054433a27b2492f7086c41b549d..d5f40c4883c8065f2af9803575f2957dd305b3ca 100644
--- a/src/structures/services/structures.service.spec.ts
+++ b/src/structures/services/structures.service.spec.ts
@@ -615,4 +615,28 @@ describe('StructuresService', () => {
       expect(result).toEqual(expectedResult);
     });
   });
+  describe('getAvailablePermalink', () => {
+    it('should return a permalink', async () => {
+      jest.spyOn(structureService, 'findByPermalink').mockResolvedValueOnce(null);
+      const permalink = await structureService.getAvailablePermalink('test');
+      expect(permalink).toBe('test');
+    });
+    it('should return a permalink with "-1" because structure already exists', async () => {
+      jest
+        .spyOn(structureService, 'findByPermalink')
+        .mockResolvedValueOnce(mockResinStructures[0] as StructureDocument)
+        .mockResolvedValueOnce(null);
+      const permalink = await structureService.getAvailablePermalink('test');
+      expect(permalink).toBe('test-1');
+    });
+    it('should return a permalink with "-2" because structure already exists twice', async () => {
+      jest
+        .spyOn(structureService, 'findByPermalink')
+        .mockResolvedValueOnce(mockResinStructures[0] as StructureDocument)
+        .mockResolvedValueOnce(mockResinStructures[0] as StructureDocument)
+        .mockResolvedValueOnce(null);
+      const permalink = await structureService.getAvailablePermalink('test');
+      expect(permalink).toBe('test-2');
+    });
+  });
 });
diff --git a/src/structures/services/structures.service.ts b/src/structures/services/structures.service.ts
index 019fef247999b7a88af191552f29f251b4083eaf..64c304a7f10e65a7ecd60e6afe5ac9fda0cebb79 100644
--- a/src/structures/services/structures.service.ts
+++ b/src/structures/services/structures.service.ts
@@ -14,8 +14,12 @@ import { Module } from '../../categories/schemas/module.class';
 import { CategoriesService } from '../../categories/services/categories.service';
 import { MailerService } from '../../mailer/mailer.service';
 import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema';
+import { sanitize } from '../../shared/utils';
 import { IUser } from '../../users/interfaces/user.interface';
+import { User } from '../../users/schemas/user.schema';
+import { JobsService } from '../../users/services/jobs.service';
 import { UsersService } from '../../users/services/users.service';
+import { PhotonAddress } from '../common/photonAddress.type';
 import { PhotonResponse } from '../common/photonResponse.type';
 import { depRegex } from '../common/regex';
 import { StructureDto } from '../dto/structure.dto';
@@ -26,9 +30,6 @@ import { PhotonPoints } from '../interfaces/photon-response.interface';
 import { Address } from '../schemas/address.schema';
 import { Structure, StructureDocument } from '../schemas/structure.schema';
 import { StructuresSearchService } from './structures-search.service';
-import { JobsService } from '../../users/services/jobs.service';
-import { User } from '../../users/schemas/user.schema';
-import { PhotonAddress } from '../common/photonAddress.type';
 
 @Injectable()
 export class StructuresService {
@@ -244,6 +245,7 @@ export class StructuresService {
     const createdStructure = new this.structureModel(structure);
     createdStructure._id = new Types.ObjectId();
     createdStructure.structureName = createdStructure.structureName.trim();
+    createdStructure.permalink = await this.getAvailablePermalink(createdStructure.structureName);
     createdStructure.categories.selfServiceMaterial = this.getSelfServiceMaterial(createdStructure);
     await createdStructure.save();
     await this.setStructurePosition(createdStructure).then(async (structureWithPosition: StructureDocument) => {
@@ -275,6 +277,22 @@ export class StructuresService {
     return updatedStructure;
   }
 
+  public async getAvailablePermalink(structureName: string): Promise<string> {
+    let permalink = sanitize(structureName);
+
+    // If already exists, add a number to the end of the permalink
+    const exists = await this.findByPermalink(permalink);
+    if (exists) {
+      let count = 1;
+      while (await this.findByPermalink(`${permalink}-${count}`)) {
+        count++;
+      }
+      permalink = `${permalink}-${count}`;
+    }
+
+    return permalink;
+  }
+
   public async search(searchString: string, filters?: Array<any>): Promise<Structure[]> {
     if (searchString && filters) {
       return this.structureModel
@@ -456,13 +474,20 @@ export class StructuresService {
     return responseStructure;
   }
 
+  private async findQueryExec(query): Promise<StructureDocument> {
+    return query.populate('personalOffers').populate('structureType').exec();
+  }
+
   public async findOne(idParam: string): Promise<StructureDocument> {
-    this.logger.debug('findOne');
-    return this.structureModel
-      .findById(new Types.ObjectId(idParam))
-      .populate('personalOffers')
-      .populate('structureType')
-      .exec();
+    this.logger.debug(`findOne: ${idParam}`);
+    const structureQuery = this.structureModel.findById(new Types.ObjectId(idParam));
+    return this.findQueryExec(structureQuery);
+  }
+
+  public async findByPermalink(permalink: string): Promise<StructureDocument> {
+    this.logger.debug(`findByPermalink: ${permalink}`);
+    const structureQuery = this.structureModel.findOne({ permalink });
+    return this.findQueryExec(structureQuery);
   }
 
   /**
@@ -914,7 +939,7 @@ export class StructuresService {
     const jsonConfig = this.mailerService.loadJsonConfig(jsonConfigLocation);
     const html = await ejs.renderFile(ejsPath, {
       config,
-      id: structure ? structure._id : 0,
+      permalink: structure ? structure.permalink : '',
       structureName: structure ? structure.structureName : '',
       structureAddress: structure
         ? `${structure.address.numero || ''} ${structure.address.street} ${structure.address.commune}`
@@ -1089,7 +1114,7 @@ export class StructuresService {
     const html = await ejs.renderFile(ejsPath, {
       config,
       content: content,
-      id: structure._id,
+      permalink: structure.permalink,
       structureName: structure.structureName,
     });
     this.mailerService.send(emailsObject, jsonConfig.subject, html);
diff --git a/src/structures/structures.controller.spec.ts b/src/structures/structures.controller.spec.ts
index 2ca77c731f722ae6783a603a44d2a677f9a6ad8d..d480497671181fdb5a1a93260a1c54bf6dd84efc 100644
--- a/src/structures/structures.controller.spec.ts
+++ b/src/structures/structures.controller.spec.ts
@@ -1,30 +1,30 @@
 import { HttpService } from '@nestjs/axios';
 import { HttpStatus } from '@nestjs/common';
+import { getModelToken } from '@nestjs/mongoose';
 import { Test } from '@nestjs/testing';
 import { of } from 'rxjs';
 import { PhotonResponseMock } from '../../test/mock/data/dataPhoton.mock.data';
 import { structureDtoMock } from '../../test/mock/data/structure.mock.dto';
+import { mockDeletedStructure, mockStructure } from '../../test/mock/data/structures.mock.data';
+import { mockUser } from '../../test/mock/data/users.mock.data';
 import { CategoriesServiceMock } from '../../test/mock/services/categories.mock.service';
+import {
+  StructuresExportServiceMock,
+  mockFormattedStructures,
+} from '../../test/mock/services/structures-export.mock.service';
 import { UsersServiceMock } from '../../test/mock/services/user.mock.service';
 import { CategoriesService } from '../categories/services/categories.service';
 import { PersonalOffersService } from '../personal-offers/personal-offers.service';
 import { TempUserService } from '../temp-user/temp-user.service';
 import { UserRole } from '../users/enum/user-role.enum';
+import { JobDocument } from '../users/schemas/job.schema';
 import { UsersService } from '../users/services/users.service';
 import { CreateStructureDto } from './dto/create-structure.dto';
-import { StructuresService } from './services/structures.service';
-import { StructuresController } from './structures.controller';
-import { mockDeletedStructure, mockStructure } from '../../test/mock/data/structures.mock.data';
-import { mockUser } from '../../test/mock/data/users.mock.data';
-import { StructuresExportService } from './services/structures-export.service';
-import {
-  StructuresExportServiceMock,
-  mockFormattedStructures,
-} from '../../test/mock/services/structures-export.mock.service';
 import { StructureFormatted } from './interfaces/structure-formatted.interface';
-import { JobDocument } from '../users/schemas/job.schema';
-import { getModelToken } from '@nestjs/mongoose';
 import { Structure } from './schemas/structure.schema';
+import { StructuresExportService } from './services/structures-export.service';
+import { StructuresService } from './services/structures.service';
+import { StructuresController } from './structures.controller';
 
 describe('StructuresController', () => {
   let structuresController: StructuresController;
@@ -49,6 +49,7 @@ describe('StructuresController', () => {
     searchForStructures: jest.fn(),
     update: jest.fn(),
     updateAllDenormalizedFields: jest.fn(),
+    findByPermalink: jest.fn(),
   };
 
   const mockTempUserService = {
@@ -200,6 +201,7 @@ describe('StructuresController', () => {
       } as JobDocument,
       unattachedSince: null,
       lastLoginDate: null,
+      permalink: '',
     });
     expect(res).toBeTruthy();
   });
@@ -327,6 +329,24 @@ describe('StructuresController', () => {
     // test personal offers delete
   });
 
+  describe('permalink', () => {
+    it('should get structure by permalink', async () => {
+      mockStructureService.findByPermalink.mockResolvedValueOnce(mockStructure);
+      await structuresController.findByPermalink({ user: { _id: 'test' } }, 'test');
+      expect(mockStructureService.findByPermalink).toHaveBeenCalled();
+    });
+    it('should throw error if no structure found', async () => {
+      mockStructureService.findByPermalink.mockResolvedValueOnce(null);
+      try {
+        await structuresController.findByPermalink({ user: { _id: 'test' } }, 'test');
+        expect(true).toBe(false);
+      } catch (error) {
+        expect(error.message).toBe('Structure does not exist');
+        expect(error.status).toBe(HttpStatus.NOT_FOUND);
+      }
+    });
+  });
+
   it('should remove temp user', async () => {
     mockStructureService.findOne.mockResolvedValue(mockStructure);
     mockTempUserService.findById.mockResolvedValue(mockUser);
diff --git a/src/structures/structures.controller.ts b/src/structures/structures.controller.ts
index 7ba449e7479cbbd21193cfb94f4719e5b04c3458..fe62766f74a30f95c36ff8c3bea30f4a40461d1e 100644
--- a/src/structures/structures.controller.ts
+++ b/src/structures/structures.controller.ts
@@ -38,10 +38,10 @@ import { CreateStructureDto } from './dto/create-structure.dto';
 import { QueryStructure } from './dto/query-structure.dto';
 import { UpdateStructureDto } from './dto/update-structure.dto';
 import { PhotonPoints } from './interfaces/photon-response.interface';
+import { StructureFormatted } from './interfaces/structure-formatted.interface';
 import { Structure, StructureDocument } from './schemas/structure.schema';
 import { StructuresExportService } from './services/structures-export.service';
 import { StructuresService } from './services/structures.service';
-import { StructureFormatted } from './interfaces/structure-formatted.interface';
 
 @ApiTags('structures')
 @Controller('structures')
@@ -171,6 +171,18 @@ export class StructuresController {
     return structure;
   }
 
+  @Get('permalink/:permalink')
+  @UseGuards(AuthGuard(['jwt', 'anonymous'])) // first success wins (allow anonymous call, req.user is an empty object if user is not authenticated)
+  public async findByPermalink(@Request() req, @Param('permalink') permalink: string) {
+    this.logger.debug(`findByPermalink with ${permalink}`);
+    const structure = await this.structureService.findByPermalink(permalink);
+    if (!structure || (structure.deletedAt && !hasAdminRole(req.user))) {
+      this.logger.log(`structure with permalink ${permalink} does not exist`);
+      throw new HttpException('Structure does not exist', HttpStatus.NOT_FOUND);
+    }
+    return structure;
+  }
+
   @Get(':id/withOwners')
   public async findWithOwners(@Param('id') id: string) {
     return this.structureService.findWithOwners(id);
diff --git a/src/users/controllers/users.controller.spec.ts b/src/users/controllers/users.controller.spec.ts
index 8b2e72bebcaea572671ab4c4605aab1fddacd93f..9df155d9b561b69733aa6a89788609b1258b283c 100644
--- a/src/users/controllers/users.controller.spec.ts
+++ b/src/users/controllers/users.controller.spec.ts
@@ -58,12 +58,12 @@ describe('UsersController', () => {
     validateUser: jest.fn(),
     verifyAndUpdateUserEmail: jest.fn(),
     verifyUserExist: jest.fn(),
+    findByPermalink: jest.fn(),
     checkPasswordResetToken: jest.fn(),
     updatePendingStructureLinked: jest.fn(),
     updateStructureLinked: jest.fn(),
     removeFromPendingStructureLinked: jest.fn(),
     sendStructureClaimApproval: jest.fn(),
-    sendAdminStructureNotification: jest.fn(),
   };
 
   const structureServiceMock = {
@@ -443,7 +443,7 @@ describe('UsersController', () => {
       }
     });
 
-    it('should accept and return id & name', async () => {
+    it('should accept and return structure', async () => {
       mockJwtService.decode.mockReturnValue({
         expiresAt: '2999-12-31T00:00:00.000Z',
         idStructure: '620e5236f25755550cb86dfd',
@@ -465,10 +465,10 @@ describe('UsersController', () => {
       const result = await usersController.joinValidation('token', 'true');
       expect(userServiceMock.updateStructureLinked).toHaveBeenCalledTimes(1);
       expect(userServiceMock.removeFromPendingStructureLinked).toHaveBeenCalledTimes(1);
-      expect(result).toEqual({ id: '620e5236f25755550cb86dfd', name: 'a' });
+      expect(result).toEqual(mockStructure);
     });
 
-    it('should refuse and return id & name', async () => {
+    it('should refuse and return structure', async () => {
       mockJwtService.decode.mockReturnValue({
         expiresAt: '2999-12-31T00:00:00.000Z',
         idStructure: '620e5236f25755550cb86dfd',
@@ -490,7 +490,7 @@ describe('UsersController', () => {
       const result = await usersController.joinValidation('token', 'false');
       expect(userServiceMock.updateStructureLinked).toHaveBeenCalledTimes(0);
       expect(userServiceMock.removeFromPendingStructureLinked).toHaveBeenCalledTimes(1);
-      expect(result).toEqual({ id: '620e5236f25755550cb86dfd', name: 'a' });
+      expect(result).toEqual(mockStructure);
     });
   });
 
@@ -548,4 +548,23 @@ describe('UsersController', () => {
       expect(userServiceMock.removeFromPendingStructureLinked).toHaveBeenCalledTimes(1);
     });
   });
+
+  describe('permalink', () => {
+    it('should get user by permalink', async () => {
+      userServiceMock.findByPermalink.mockResolvedValueOnce(usersMockData[0]);
+      const result = await usersController.getUserByPermalink('test');
+      expect(userServiceMock.findByPermalink).toHaveBeenCalled();
+      expect(result).toEqual(usersMockData[0]);
+    });
+    it('should throw error if no user found', async () => {
+      userServiceMock.findByPermalink.mockResolvedValueOnce(null);
+      try {
+        await usersController.getUserByPermalink('test');
+        expect(true).toBe(false);
+      } catch (error) {
+        expect(error.message).toBe('User does not exist');
+        expect(error.status).toBe(HttpStatus.NOT_FOUND);
+      }
+    });
+  });
 });
diff --git a/src/users/controllers/users.controller.ts b/src/users/controllers/users.controller.ts
index 2be10cb18e70d2da5f0acd00ab0ced00c030447a..359fe25f59968fb60ea77dfde93a574bc369085e 100644
--- a/src/users/controllers/users.controller.ts
+++ b/src/users/controllers/users.controller.ts
@@ -207,6 +207,17 @@ export class UsersController {
     return user;
   }
 
+  @UseGuards(JwtAuthGuard)
+  @Get('permalink/:permalink')
+  @ApiParam({ name: 'permalink', type: String, required: true })
+  public async getUserByPermalink(@Param('permalink') permalink: string): Promise<IUser> {
+    const user = await this.usersService.findByPermalink(permalink);
+    if (!user) {
+      throw new HttpException('User does not exist', HttpStatus.NOT_FOUND);
+    }
+    return user;
+  }
+
   @ApiResponse({ status: HttpStatus.CREATED, description: 'Description updated' })
   @ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized' })
   @UseGuards(JwtAuthGuard)
@@ -249,13 +260,7 @@ export class UsersController {
   @Get('join-validate/:token/:status')
   @ApiParam({ name: 'token', type: String, required: true })
   @ApiParam({ name: 'status', type: String, required: true })
-  public async joinValidation(
-    @Param('token') token: string,
-    @Param('status') status: string
-  ): Promise<{
-    id: string;
-    name: string;
-  }> {
+  public async joinValidation(@Param('token') token: string, @Param('status') status: string): Promise<Structure> {
     const decoded = this.jwtService.decode(token) as IPendingStructureToken;
     const today = DateTime.local().setZone('utc', { keepLocalTime: true });
 
@@ -292,7 +297,7 @@ export class UsersController {
     }
     await this.usersService.sendStructureClaimApproval(userFromDb.email, structure.structureName, status == 'true');
 
-    return { id: decoded.idStructure, name: structure.structureName };
+    return structure;
   }
 
   /** Cancel a user's join request */
diff --git a/src/users/schemas/user.schema.ts b/src/users/schemas/user.schema.ts
index c850621c819deb256743bb8edaa1a4d18115c7a5..cafd214ba94e6ee1826e95884c4301f63c744788 100644
--- a/src/users/schemas/user.schema.ts
+++ b/src/users/schemas/user.schema.ts
@@ -1,10 +1,10 @@
 import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
 import { Types } from 'mongoose';
 import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema';
-import { Employer } from './employer.schema';
-import { JobDocument } from './job.schema';
 import { UserRole } from '../enum/user-role.enum';
 import { pendingStructuresLink } from '../interfaces/pendingStructure';
+import { Employer } from './employer.schema';
+import { JobDocument } from './job.schema';
 
 @Schema({ timestamps: true })
 export class User {
@@ -74,6 +74,9 @@ export class User {
   @Prop({ default: null })
   lastLoginDate: Date;
 
+  @Prop()
+  permalink: string;
+
   // Document methods
   // static because object method would need to create a constructor and get an actual User instance (cf. https://stackoverflow.com/a/42899705 )
 
diff --git a/src/users/services/userRegistry.service.ts b/src/users/services/userRegistry.service.ts
index be12c62b11eb08a8c18c39ef570af648ab7095ff..2577369ed616d1d25892205911429a1530edd535 100644
--- a/src/users/services/userRegistry.service.ts
+++ b/src/users/services/userRegistry.service.ts
@@ -4,9 +4,9 @@ import { Model } from 'mongoose';
 import { IUser } from '../interfaces/user.interface';
 import { IUserRegistry, UserRegistryPaginatedResponse } from '../interfaces/userRegistry.interface';
 import { Employer } from '../schemas/employer.schema';
+import { JobsGroupsDocument } from '../schemas/jobsGroups.schema';
 import { User } from '../schemas/user.schema';
 import { EmployerService } from './employer.service';
-import { JobsGroupsDocument } from '../schemas/jobsGroups.schema';
 import { JobsGroupsService } from './jobsGroups.service';
 import { UserRegistrySearchService } from './userRegistry-search.service';
 
@@ -93,7 +93,7 @@ export class UserRegistryService {
       })
       .where('emailVerified')
       .equals(true)
-      .select('name surname employer job _id withAppointment')
+      .select('name surname employer job _id withAppointment permalink')
       .populate('employer job')
       .collation({ locale: 'fr' })
       .sort({ surname: 1 })
diff --git a/src/users/services/users.service.spec.ts b/src/users/services/users.service.spec.ts
index 2a84af964132d8b0d9e6a7afb4d407b33331dd4a..6241aacde355ea80bb3dfba8b7051e43fa722c19 100644
--- a/src/users/services/users.service.spec.ts
+++ b/src/users/services/users.service.spec.ts
@@ -9,11 +9,13 @@ import { employersMockData } from '../../../test/mock/data/employers.mock.data';
 import { personalOffersDataMock } from '../../../test/mock/data/personalOffers.mock.data';
 import { userDetails, userRegistryMockData, usersMockData } from '../../../test/mock/data/users.mock.data';
 import { MailerMockService } from '../../../test/mock/services/mailer.mock.service';
+import { NewsletterServiceMock } from '../../../test/mock/services/newsletter.mock.service';
 import { StructuresServiceMock } from '../../../test/mock/services/structures.mock.service';
 import { LoginDto } from '../../auth/login-dto';
 import { ConfigurationModule } from '../../configuration/configuration.module';
 import { MailerModule } from '../../mailer/mailer.module';
 import { MailerService } from '../../mailer/mailer.service';
+import { NewsletterService } from '../../newsletter/newsletter.service';
 import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema';
 import { StructuresService } from '../../structures/services/structures.service';
 import { CreateUserDto } from '../dto/create-user.dto';
@@ -26,8 +28,6 @@ import { User } from '../schemas/user.schema';
 import { JobsService } from './jobs.service';
 import { UserRegistrySearchService } from './userRegistry-search.service';
 import { UsersService } from './users.service';
-import { NewsletterService } from '../../newsletter/newsletter.service';
-import { NewsletterServiceMock } from '../../../test/mock/services/newsletter.mock.service';
 
 function hashPassword() {
   return bcrypt.hashSync(process.env.USER_PWD, process.env.SALT);
@@ -121,6 +121,7 @@ const mockUser: User = {
   } as JobDocument,
   unattachedSince: null,
   lastLoginDate: null,
+  permalink: 'jacques-dupont',
 };
 
 const mockJobsService = {
@@ -204,8 +205,6 @@ describe('UsersService', () => {
       jest.spyOn(usersService, 'isStrongPassword').mockImplementationOnce(() => true);
       //TODO mock new userModal(createUserDto)
       return;
-      jest.spyOn(usersService, 'findOne').mockImplementationOnce(async (): Promise<any> => mockUser);
-      expect(await usersService.create(createUserDto)).toBe(mockUser);
     });
   });
 
@@ -261,13 +260,6 @@ describe('UsersService', () => {
       jest.spyOn(usersService, 'findOne').mockResolvedValue(mockUser as IUser);
       //TODO mock private function comparePassword ? -> false
       return true;
-      try {
-        await usersService.checkLogin(loginDto);
-        expect(true).toBe(false);
-      } catch (error) {
-        expect(error.status).toBe(HttpStatus.UNAUTHORIZED);
-        expect(error.message).toBe('Invalid credentials');
-      }
     });
 
     it('should find', async () => {
@@ -275,7 +267,6 @@ describe('UsersService', () => {
       jest.spyOn(usersService, 'findOne').mockResolvedValue(mockUser as IUser);
       //TODO mock private function comparePassword ? -> true
       return true;
-      expect(await usersService.checkLogin(loginDto)).toBe(mockUser);
     });
 
     it('wrong password, should be unauthorized issue', async () => {
@@ -499,4 +490,26 @@ describe('UsersService', () => {
       expect(res.length).toBe(1);
     });
   });
+
+  describe('getAvailablePermalink', () => {
+    it('should return a permalink', async () => {
+      jest.spyOn(usersService, 'findByPermalink').mockResolvedValueOnce(null);
+      const permalink = await usersService.getAvailablePermalink('prenom', 'nom');
+      expect(permalink).toBe('prenom-nom');
+    });
+    it('should return a permalink with "-1" because structure already exists', async () => {
+      jest.spyOn(usersService, 'findByPermalink').mockResolvedValueOnce(usersMockData[0]).mockResolvedValueOnce(null);
+      const permalink = await usersService.getAvailablePermalink('prenom', 'nom');
+      expect(permalink).toBe('prenom-nom-1');
+    });
+    it('should return a permalink with "-2" because structure already exists twice', async () => {
+      jest
+        .spyOn(usersService, 'findByPermalink')
+        .mockResolvedValueOnce(usersMockData[0])
+        .mockResolvedValueOnce(usersMockData[0])
+        .mockResolvedValueOnce(null);
+      const permalink = await usersService.getAvailablePermalink('prenom', 'nom');
+      expect(permalink).toBe('prenom-nom-2');
+    });
+  });
 });
diff --git a/src/users/services/users.service.ts b/src/users/services/users.service.ts
index fcd3e2d10bcbbb6833bc318b64d27a03673c73d0..abdbc5f4ca472453329351dead90509bb02c62a4 100644
--- a/src/users/services/users.service.ts
+++ b/src/users/services/users.service.ts
@@ -14,6 +14,7 @@ import { LoginDto } from '../../auth/login-dto';
 import { MailerService } from '../../mailer/mailer.service';
 import { NewsletterService } from '../../newsletter/newsletter.service';
 import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema';
+import { sanitize } from '../../shared/utils';
 import { Structure, StructureDocument } from '../../structures/schemas/structure.schema';
 import { StructuresService } from '../../structures/services/structures.service';
 import { EmailChangeDto } from '../dto/change-email.dto';
@@ -60,6 +61,7 @@ export class UsersService {
     }
     let createUser = new this.userModel(createUserDto) as IUser;
     createUser.surname = createUser.surname.toUpperCase();
+    createUser.permalink = await this.getAvailablePermalink(createUser.name, createUser.surname);
     createUser.structuresLink = [];
     if (createUserDto.structuresLink) {
       createUserDto.structuresLink.forEach((structureId) => {
@@ -77,6 +79,21 @@ export class UsersService {
     return this.findOne(createUserDto.email);
   }
 
+  public async getAvailablePermalink(name: string, surname: string): Promise<string> {
+    let permalink = sanitize(`${name}-${surname}`);
+
+    // If already exists, add a number to the end of the permalink
+    const exists = await this.findByPermalink(permalink);
+    if (exists) {
+      let count = 1;
+      while (await this.findByPermalink(`${permalink}-${count}`)) {
+        count++;
+      }
+      permalink = `${permalink}-${count}`;
+    }
+
+    return permalink;
+  }
   /**
    * Verify password strength with the following rule:
    * - The string must contain at least 1 lowercase alphabetical character
@@ -105,6 +122,10 @@ export class UsersService {
     return this.userModel.findOne({ email: mail }).populate('employer').populate('job').select('-password').exec();
   }
 
+  public async findByPermalink(permalink: string): Promise<IUser | undefined> {
+    return this.userModel.findOne({ permalink }).exec();
+  }
+
   public findAll(): Promise<User[]> {
     return this.userModel.find().populate('employer').populate('job').select('-password').exec();
   }
@@ -288,7 +309,7 @@ export class UsersService {
       config,
       name: user.name,
       surname: user.surname,
-      userId: user._id,
+      userPermalink: user.permalink,
     });
     this.logger.debug(html);
     const admins = await this.getAdmins();
@@ -749,7 +770,7 @@ export class UsersService {
       .populate('job')
       .populate('employer')
       .populate('personalOffers')
-      .select('name surname job employer email withAppointment personalOffers')
+      .select('name surname permalink job employer email withAppointment personalOffers')
       .exec();
   }
 
diff --git a/test/mock/data/structures.mock.data.ts b/test/mock/data/structures.mock.data.ts
index 18d7efcdb7858edd82182532386c129de4c4fee4..a830061d6d7a3254e0350143c0b2baa4ee6cc200 100644
--- a/test/mock/data/structures.mock.data.ts
+++ b/test/mock/data/structures.mock.data.ts
@@ -422,6 +422,7 @@ export const mockStructure: Structure = {
   freeWorkShop: 'Non',
   accountVerified: true,
   categoriesWithPersonalOffers: {},
+  permalink: 'a',
 };
 
 export const mockDeletedStructure: Structure = {
@@ -527,4 +528,5 @@ export const mockDeletedStructure: Structure = {
   createdAt: new Date('2020-11-16T09:30:00.000Z'),
   updatedAt: new Date('2020-11-16T09:30:00.000Z'),
   deletedAt: new Date('2020-11-16T09:30:00.000Z'),
+  permalink: 'latelier-numerique',
 };
diff --git a/test/mock/data/users.mock.data.ts b/test/mock/data/users.mock.data.ts
index 2dfe3fde3a243ed607c6c78c5a45fb049fe63cfc..b779604ede304d59c609a154dc984eb023c5acca 100644
--- a/test/mock/data/users.mock.data.ts
+++ b/test/mock/data/users.mock.data.ts
@@ -244,4 +244,5 @@ export const mockUser: User = {
   structureOutdatedMailSent: [],
   unattachedSince: new Date(2024, 1, 1),
   lastLoginDate: new Date(2024, 1, 1),
+  permalink: 'pauline-dupont',
 };
diff --git a/test/mock/services/structures.mock.service.ts b/test/mock/services/structures.mock.service.ts
index 63bb0cf7cc1f3204d0d98a1c6f4eeaa29c1b06e6..8ee9c3c3575229205228d9bc0dfb5c674df5fea8 100644
--- a/test/mock/services/structures.mock.service.ts
+++ b/test/mock/services/structures.mock.service.ts
@@ -1547,6 +1547,7 @@ export const mockResinStructures: Array<Structure> = [
     dataShareConsentDate: new Date('2021-05-06T09:42:38.000Z'),
     personalOffers: [],
     categoriesWithPersonalOffers: null,
+    permalink: 'a',
   },
   {
     contactMail: 'matchin@email.com',
@@ -1649,6 +1650,7 @@ export const mockResinStructures: Array<Structure> = [
     deletedAt: new Date('2121-05-06T09:42:38.000Z'),
     dataShareConsentDate: new Date('2021-05-06T09:42:38.000Z'),
     categoriesWithPersonalOffers: null,
+    permalink: 'a-1',
   },
   {
     contactMail: 'nomatch@email.com',
@@ -1751,5 +1753,6 @@ export const mockResinStructures: Array<Structure> = [
     deletedAt: new Date('2121-05-06T09:42:38.000Z'),
     dataShareConsentDate: new Date('2021-05-06T09:42:38.000Z'),
     categoriesWithPersonalOffers: null,
+    permalink: 'a-2',
   },
 ];