|
| 1 | +# Pre-request feedback with magic link |
| 2 | + |
| 3 | +## User story |
| 4 | + |
| 5 | +As a Zenika employee, I would like to request feedback from colleagues when I don't know their email addresses upfront. Instead of specifying individual emails, I can generate a shareable "magic link" that colleagues can use to provide their email address and trigger a feedback request. |
| 6 | + |
| 7 | +This is particularly useful when: |
| 8 | + |
| 9 | +- I want to share the request in a Slack channel or Teams group |
| 10 | +- My colleagues' company blocks emails sent via Mailgun. However, they can still give me feedback using their personal email addresses, which I don't know in advance. |
| 11 | + |
| 12 | +Once a colleague uses the magic link and provides their email, they receive a feedback request email just like in the standard feedback request workflow. |
| 13 | + |
| 14 | +## Technical specifications |
| 15 | + |
| 16 | +Be sure to read [Request feedback](./request-feedback) first, as this feature is an enhancement of that workflow. |
| 17 | + |
| 18 | +### Magic link generation workflow |
| 19 | + |
| 20 | +1. The requester (authenticated user) navigates to `/request` |
| 21 | +2. Selects **"With a magic link"** method |
| 22 | +3. Fills and Submits the form (the recipients field is disabled for this method) |
| 23 | +4. Client calls `POST /feedback/pre-request/token` |
| 24 | +5. Server creates a document in the `feedbackPreRequestToken` collection and returns `{ token: string }` |
| 25 | +6. Client navigates to the success page displaying the magic link |
| 26 | + |
| 27 | +The requester can copy this link and share it through any channel (Slack, Teams, email, etc.). |
| 28 | + |
| 29 | +### Firestore schema |
| 30 | + |
| 31 | +A document is added in the `feedbackPreRequestToken` collection to store magic link tokens: |
| 32 | + |
| 33 | +```ts |
| 34 | +const preRequestToken: FeedbackPreRequestToken = { |
| 35 | + receiverEmail: 'pinocchio@zenika.com', // Who will receive the feedback |
| 36 | + message: 'Hi team, please share your feedback...', // Encrypted |
| 37 | + shared: true, // Whether to share with manager |
| 38 | + expiresAt: 1711662999463, // Timestamp |
| 39 | + usedBy: ['gimini@gmail.com', 'gepetto@gmail.com'], // Emails that used this token |
| 40 | +}; |
| 41 | +``` |
| 42 | + |
| 43 | +- The document ID is the token itself (auto-generated by Firestore). |
| 44 | +- The `usedBy` array tracks which emails have already used this token |
| 45 | +- Each use of the token triggers a separate feedback request |
| 46 | + |
| 47 | +### Magic link format |
| 48 | + |
| 49 | +The magic link format is: `{origin}/pre-request/token/{token}` |
| 50 | + |
| 51 | +**Example:** |
| 52 | + |
| 53 | +```txt |
| 54 | +https://feedzback.znk.io/pre-request/token/abc123xyz |
| 55 | +``` |
| 56 | + |
| 57 | +The magic link does not contain the locale. |
| 58 | +The redirection to `/fr` or `/en` occurs when the colleague visits the magic link page (see `src/404.html` for details). |
| 59 | + |
| 60 | +However, since this redirection is not functional in the `dev-local` environment, the locale is explicitly added only in this case. |
| 61 | + |
| 62 | +**Example in `dev-local` environment:** |
| 63 | + |
| 64 | +```txt |
| 65 | +https://feedzback.znk.io/fr/pre-request/token/abc123xyz |
| 66 | +``` |
| 67 | + |
| 68 | +### Magic link usage workflow |
| 69 | + |
| 70 | +When a colleague accesses the magic link: |
| 71 | + |
| 72 | +1. The colleague (not authenticated) visits `/pre-request/token/{token}` |
| 73 | +2. The colleague's browser redirects to the appropriate locale (example: `/fr/pre-request/token/{token}`) |
| 74 | +3. Client calls `GET /feedback/check-pre-request/{token}` to validate the token |
| 75 | +4. If valid, the page displays the details of the pre-request and a form field allowing the colleague to enter their email address |
| 76 | +5. The colleague enters their email and submits |
| 77 | +6. Client calls `POST /feedback/pre-request/email` with `{ token, recipient }` |
| 78 | +7. Server validates the token and email, then: |
| 79 | + - Adds the email to the `usedBy` array in `feedbackPreRequestToken` document |
| 80 | + - Triggers the standard feedback request workflow: |
| 81 | + - Creates `feedback` and `feedbackRequestToken` documents |
| 82 | + - Sends a feedback request email to the colleague |
| 83 | +8. Client navigates to a success page confirming the email was sent |
| 84 | + |
| 85 | +From this point forward, the flow is identical to the standard [Reply to feedback request](./reply-to-feedback-request) workflow. |
| 86 | + |
| 87 | +### Token constraints |
| 88 | + |
| 89 | +- **Expiration** configured via `FEEDBACK_PRE_REQUEST_EXPIRATION_IN_DAYS` constant (3 days) |
| 90 | +- **Maximum uses** configured via `FEEDBACK_PRE_REQUEST_MAX_USES` constant (10 uses per token) |
| 91 | + |
| 92 | +When a colleague attempts to use a magic link, the following checks are performed: |
| 93 | + |
| 94 | +| Validation | Error Type | Description | |
| 95 | +| ---------------- | ------------------------ | ----------------------------------------------- | |
| 96 | +| Token exists | `token_invalid` | The token doesn't exist in the database | |
| 97 | +| Not expired | `token_expired` | The current time exceeds `expiresAt` | |
| 98 | +| Under max uses | `token_max_uses_reached` | The `usedBy` array length has reached the limit | |
| 99 | +| Email not used | `recipient_already_used` | The email is already in the `usedBy` array | |
| 100 | +| Not self-request | `recipient_forbidden` | The email matches the `receiverEmail` | |
| 101 | + |
| 102 | +If any validation fails, a `BadRequestException` or `ForbiddenException` is thrown with the corresponding error type. |
| 103 | + |
| 104 | +## Links |
| 105 | + |
| 106 | +- **Client** |
| 107 | + - [`RequestFeedbackComponent`](https://github.com/Zenika/feedzback/blob/main/client/src/app/request-feedback/request-feedback.component.ts) |
| 108 | + - [`PreRequestFeedbackComponent`](https://github.com/Zenika/feedzback/blob/main/client/src/app/pre-request-feedback/pre-request-feedback.component.ts) |
| 109 | +- **Server** |
| 110 | + - [`FeedbackController`](https://github.com/Zenika/feedzback/blob/main/server/src/feedback/feedback.controller.ts) |
| 111 | + - `preRequestToken` - Creates pre-request token |
| 112 | + - `checkPreRequest` - Validates token and returns details |
| 113 | + - `preRequestEmail` - Processes email submission and triggers feedback request |
0 commit comments