BIO.RE
Notifications

Email Unsubscribe (Public)

Public HMAC-token endpoint reachable from email footers without login. Adds the email to the suppression list AND disables the email channel in every event preference for the matching user.

GET /api/v1/notifications/unsubscribe โ€” ๐ŸŒ Public ยท Rate limit: 10 req / hour

Public unsubscribe link target. Reachable from email footers without login. Validates a server-signed HMAC token (sha256 over the email address, truncated to 32 hex chars), then adds the email to the suppression list and flips email: false in every notification preference for the matching user.

HMAC signature is the auth. The token must equal sha256(UNSUBSCRIBE_HMAC_SECRET + email).slice(0,32) (server-side env). An incorrect token returns 200 OK with success: false (NOT a 403/404 โ€” to avoid email-enumeration via timing).

Two-pass effect on success:

  1. Insert into EmailSuppressionList (idempotent โ€” findUnique first to skip duplicates) with reason: 'user_unsubscribe'. The email-dispatch pipeline checks this list before sending โ€” once suppressed, no future emails go out for that address.
  2. For every eventKey in NotificationDefault, upsert the user's NotificationPreference row with email: false and other channels defaulting to true (on first save). This means the user can still receive in-app / push / SMS โ€” only email is suppressed.

If no User is found for the email (e.g. email-only subscriber), only step 1 runs.

Request

Query parameters

ParamTypeRequiredNotes
emailstringconditionalThe email being unsubscribed. Without it, the response is the friendly "go to settings" message (no error).
tokenstringconditionalHMAC signature. Without it, same friendly fallback message.

No body, no headers required.

Response

200 OK โ€” ApiResponseOf<UnsubscribeResponseDto>

Three response shapes depending on input + validity:

{
  "success": true,
  "data": {
    "message": "Please visit your account settings to manage notification preferences."
  }
}

Invalid token (HMAC mismatch)

{
  "success": true,
  "data": {
    "success": false,
    "message": "Invalid or expired unsubscribe link."
  }
}

Valid token

{
  "success": true,
  "data": {
    "success": true,
    "message": "You have been unsubscribed from email notifications."
  }
}
FieldTypeNotes
successboolean | undefinedPresent in valid + invalid-token paths; absent in the missing-params path
messagestringHuman-friendly localized message

Errors

HTTPcode / i18nKeyReason
429(throttle)Rate limit exceeded (10 req/hour โ€” tighter for the public endpoint)
500(env)UNSUBSCRIBE_HMAC_SECRET not set in production (server-side config error โ€” surfaces as generic 500 to the user)

The endpoint does not return 4xx for missing or invalid params โ€” it always returns 200 with a tailored message body. This is deliberate: email links can't enumerate addresses or distinguish "wrong token" from "wrong address".

Side effects

  1. Missing email/token โ†’ return friendly fallback message. No mutations.
  2. Compute expected token โ€” createHmac('sha256', UNSUBSCRIBE_HMAC_SECRET).update(email).digest('hex').slice(0, 32). In dev mode, falls back to 'dev-unsubscribe-secret' if env var is unset.
  3. Token mismatch โ†’ return { success: false, message: 'Invalid or expired unsubscribe link.' }. No mutations.
  4. Token valid โ€” two-pass mutation:
    • Suppress email โ€” emailSuppressionList.findUnique({ where: { email } }); if missing, create with reason: 'user_unsubscribe', provider: null. Idempotent.
    • Disable email channel for the user โ€” prisma.user.findUnique({ where: { email } }) to resolve userId; if found, iterate every notificationDefault.findMany row and notificationPreference.upsert({ update: { email: false }, create: { email: false, push: true, inApp: true, webPush: true, sms: true } }) for each eventKey.
  5. Return { success: true, message: 'You have been unsubscribed from email notifications.' }.

Code samples

# Direct call (server-generated token)
curl 'https://api.bio.re/api/v1/notifications/[email protected]&token=abc123def456...32chars'

# Missing-params fallback (returns friendly message)
curl 'https://api.bio.re/api/v1/notifications/unsubscribe'
type UnsubscribeResponse = {
  success?: boolean;
  message: string;
};

async function unsubscribeFromEmail(email: string, token: string): Promise<UnsubscribeResponse> {
  const url = new URL('https://api.bio.re/api/v1/notifications/unsubscribe');
  url.searchParams.set('email', email);
  url.searchParams.set('token', token);
  const res = await fetch(url);
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Unsubscribe failed'), {
      code: json?.error?.code,
    });
  }
  return json.data;
}

Try it

GET
/api/v1/notifications/unsubscribe
AuthorizationBearer <token>

In: header

Query Parameters

email?string
token?string

Response Body

application/json

curl -X GET "https://loading/api/v1/notifications/unsubscribe"
{
  "success": true,
  "data": {
    "success": true,
    "message": "string"
  }
}

Source

SourcePathLines
Controller (inline impl)apps/api-core/src/modules/notification/user-notification.controller.ts213โ€“274 (unsubscribe โ€” HMAC validate + suppression + per-event email-off upsert)
DTO (response)apps/api-core/src/modules/notification/dto/notification-client-response.dto.ts98โ€“104 (UnsubscribeResponseDto)
HMACNode.js cryptocreateHmac('sha256', secret).update(email).digest('hex').slice(0, 32)
Envserver configUNSUBSCRIBE_HMAC_SECRET (production-required; dev fallback 'dev-unsubscribe-secret')
Prisma modelspackages/prisma/prisma/schema.prismaEmailSuppressionList (@@unique([email])), NotificationDefault, NotificationPreference, User

On this page