Skip to main content

Pre-request feedback

User story

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.

This is particularly useful when:

  • I want to share the request in a Slack channel or Teams group
  • 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.

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.

Technical specifications

Be sure to read Request feedback first, as this feature is an enhancement of that workflow.

  1. The requester (authenticated user) navigates to /request
  2. Selects "With a magic link" method
  3. Fills and Submits the form (the recipients field is disabled for this method)
  4. Client calls POST /feedback/pre-request/token
  5. Server creates a document in the feedbackPreRequestToken collection and returns { token: string }
  6. Client navigates to the success page displaying the magic link

The requester can copy this link and share it through any channel (Slack, Teams, email, etc.).

Firestore schema

A document is added in the feedbackPreRequestToken collection to store magic link tokens:

const preRequestToken: FeedbackPreRequestToken = {
receiverEmail: 'pinocchio@zenika.com', // Who will receive the feedback
message: 'Hi team, please share your feedback...', // Encrypted
shared: true, // Whether to share with manager
expiresAt: 1711662999463, // Timestamp
usedBy: ['gimini@gmail.com', 'gepetto@gmail.com'], // Emails that used this token
};
  • The document ID is the token itself (auto-generated by Firestore).
  • The usedBy array tracks which emails have already used this token
  • Each use of the token triggers a separate feedback request

The magic link format is: {origin}/pre-request/token/{token}

Example:

https://feedzback.znk.io/pre-request/token/abc123xyz

The magic link does not contain the locale. The redirection to /fr or /en occurs when the colleague visits the magic link page (see src/404.html for details).

However, since this redirection is not functional in the dev-local environment, the locale is explicitly added only in this case.

Example in dev-local environment:

https://feedzback.znk.io/fr/pre-request/token/abc123xyz

When a colleague accesses the magic link:

  1. The colleague (not authenticated) visits /pre-request/token/{token}
  2. The colleague's browser redirects to the appropriate locale (example: /fr/pre-request/token/{token})
  3. Client calls GET /feedback/check-pre-request/{token} to validate the token
  4. If valid, the page displays the details of the pre-request and a form field allowing the colleague to enter their email address
  5. The colleague enters their email and submits
  6. Client calls POST /feedback/pre-request/email with { token, recipient }
  7. Server validates the token and email, then:
    • Adds the email to the usedBy array in feedbackPreRequestToken document
    • Triggers the standard feedback request workflow:
      • Creates feedback and feedbackRequestToken documents
      • Sends a feedback request email to the colleague
  8. Client navigates to a success page confirming the email was sent

From this point forward, the flow is identical to the standard Reply to feedback request workflow.

Token constraints

  • Expiration configured via FEEDBACK_PRE_REQUEST_EXPIRATION_IN_DAYS constant (3 days)
  • Maximum uses configured via FEEDBACK_PRE_REQUEST_MAX_USES constant (10 uses per token)

When a colleague attempts to use a magic link, the following checks are performed:

ValidationError TypeDescription
Token existstoken_invalidThe token doesn't exist in the database
Not expiredtoken_expiredThe current time exceeds expiresAt
Under max usestoken_max_uses_reachedThe usedBy array length has reached the limit
Email not usedrecipient_already_usedThe email is already in the usedBy array
Not self-requestrecipient_forbiddenThe email matches the receiverEmail

If any validation fails, a BadRequestException or ForbiddenException is thrown with the corresponding error type.