{"playbook":"Notifications","url":"https://agentwranglers.se/playbook/notifications","updated":"2026-10-21","license":"https://creativecommons.org/licenses/by/4.0/","credit":"Required by the licence: credit Agent Wranglers with a link to the rule or playbook you used.","how":"Each rule's How text is on its own page: add .md to its url.","rules":[{"chapter":"Channels and preferences","name":"Keep every notification type in one catalogue","level":"Recommended","rule":"Define every notification type once, in code, with its category, allowed channels, default channels and urgency. Sending code accepts only types from that catalogue.","check":"A unit test sends a type missing from the catalogue and expects an error. A second test confirms every catalogue entry has a category, an urgency and at least one channel.","url":"https://agentwranglers.se/playbook/notifications/channels-and-preferences/notification-catalogue","related":["/playbook/notifications/channels-and-preferences/notification-preferences"],"sources":[]},{"chapter":"Channels and preferences","name":"Ask before any marketing message","level":"Required","risk":"law","rule":"Send marketing on a channel only after the person opted in to that channel. Keep the proof, and keep marketing out of transactional messages.","check":"Create a new account and send every marketing type. Nothing arrives. Opt in to push only, and only push arrives. A query finds no marketing send without a consent row for that user and channel.","url":"https://agentwranglers.se/playbook/notifications/channels-and-preferences/marketing-consent","related":["/playbook/email/email-privacy/consent-records","/playbook/notifications/text-messages/sms-opt-in-confirmation"],"sources":[{"label":"ePrivacy Directive 2002/58/EC, Article 13","url":"https://eur-lex.europa.eu/eli/dir/2002/58/oj"},{"label":"GDPR Article 7, Conditions for consent","url":"https://eur-lex.europa.eu/eli/reg/2016/679/oj"}]},{"chapter":"Channels and preferences","name":"Let people choose per type and channel","level":"Recommended","rule":"Let each person turn every notification type on or off per channel, and read those choices at send time.","check":"Turn off push for `order.shipped` and trigger a shipment. In-app shows it and no push is sent. Turn it on again and the next shipment pushes.","url":"https://agentwranglers.se/playbook/notifications/channels-and-preferences/notification-preferences","related":["/playbook/email/unsubscribe-and-bounces/preference-centre","/playbook/notifications/channels-and-preferences/notification-catalogue"],"sources":[]},{"chapter":"Channels and preferences","name":"Send one message per event","level":"Recommended","rule":"Deliver each event to a person on one channel first, and move to the next channel only if they have not seen it. Never send the same news on every channel at once.","check":"Trigger a shipment and read it in the app within the wait. One in-app item exists, and no push or email is sent. Trigger the same event twice and the count stays at one.","url":"https://agentwranglers.se/playbook/notifications/channels-and-preferences/one-message-per-event","related":["/playbook/email/automation-and-agents/send-once","/playbook/notifications/channels-and-preferences/notification-digests","/playbook/notifications/push-notifications/push-ttl-and-collapse","/playbook/notifications/in-app-notifications/read-state-sync"],"sources":[]},{"chapter":"Channels and preferences","name":"Hold messages during quiet hours","level":"Recommended","rule":"Hold every non-critical push and text until the recipient's quiet hours end, in the recipient's own time zone.","check":"A unit test sets the clock to 23:30 in the user's zone and expects the next send time to be 08:00 the next morning. A second test covers a daylight saving change night.","url":"https://agentwranglers.se/playbook/notifications/channels-and-preferences/quiet-hours","related":["/playbook/app-ui/dates-and-numbers/user-time-zone","/playbook/notifications/channels-and-preferences/notification-rate-caps"],"sources":[]},{"chapter":"Channels and preferences","name":"Batch small updates into a digest","level":"Recommended","rule":"Collect low-urgency notifications over a window and send one summary, instead of one message per event.","check":"Create 10 comments for one user within an hour. The user gets one push or email that names all 10, and the pending table for that user is empty afterwards.","url":"https://agentwranglers.se/playbook/notifications/channels-and-preferences/notification-digests","related":["/playbook/notifications/channels-and-preferences/one-message-per-event"],"sources":[]},{"chapter":"Channels and preferences","name":"Cap how many messages one person gets","level":"Recommended","rule":"Set a maximum number of push and text messages per person per day, and per type per hour. Fold the overflow into a digest or the inbox.","check":"A test sends 50 events of one type to one user in a minute. At most the hourly cap for that type goes out, and the rest land in the inbox. The sends table shows no user above the cap yesterday.","url":"https://agentwranglers.se/playbook/notifications/channels-and-preferences/notification-rate-caps","related":["/playbook/notifications/channels-and-preferences/quiet-hours"],"sources":[]},{"chapter":"Channels and preferences","name":"Never text or push customers from test systems","level":"Required","risk":"data","rule":"Outside production, send push and text messages only to an allow list of test devices and phone numbers. Drop every other recipient before the provider call.","check":"A test starts the app with staging settings and sends a text to `+46701234567` and a push to an unknown token. Neither reaches the provider. The staging environment holds no live provider key.","url":"https://agentwranglers.se/playbook/notifications/channels-and-preferences/test-notification-sinks","related":["/playbook/email/email-privacy/test-mail-capture"],"sources":[]},{"chapter":"Push notifications","name":"Ask for permission after a clear action","level":"Recommended","rule":"Ask for notification permission only after the person clicks a control that says what they will get. Never ask on page load.","check":"A browser test loads every page with a fresh profile and records permission prompts. None appear before a click. A grep finds `requestPermission` only inside click handlers.","url":"https://agentwranglers.se/playbook/notifications/push-notifications/push-permission-in-context","related":["/playbook/notifications/push-notifications/web-push-vapid"],"sources":[{"label":"Notifications API Standard","url":"https://notifications.spec.whatwg.org/"}]},{"chapter":"Push notifications","name":"Send web push with VAPID and an encrypted payload","level":"Recommended","rule":"Send web push through each subscription's push service with VAPID keys (RFC 8292) and an encrypted payload (RFC 8291). Never build your own polling or socket for background alerts.","check":"Send a test push to a staging subscription. The push service answers 201, and the notification shows on the device with the payload text.","url":"https://agentwranglers.se/playbook/notifications/push-notifications/web-push-vapid","related":["/playbook/notifications/push-notifications/push-permission-in-context","/playbook/engineering/running-in-production/secrets-store","/playbook/stack/product-services/web-push-standard"],"sources":[{"label":"RFC 8030, Generic Event Delivery Using HTTP Push","url":"https://www.rfc-editor.org/rfc/rfc8030"},{"label":"RFC 8291, Message Encryption for Web Push","url":"https://www.rfc-editor.org/rfc/rfc8291"},{"label":"RFC 8292, VAPID for Web Push","url":"https://www.rfc-editor.org/rfc/rfc8292"}]},{"chapter":"Push notifications","name":"Use the current APNs and FCM interfaces","level":"Required","risk":"outage","rule":"Send to Apple devices through APNs over HTTP/2 with token-based auth, and to Android through the FCM HTTP v1 API. Never use the retired legacy FCM API.","check":"Grep the code for `fcm.googleapis.com/fcm/send` and for certificate-based APNs settings. Neither appears. A staging send to one iOS and one Android device succeeds.","url":"https://agentwranglers.se/playbook/notifications/push-notifications/apns-fcm-current-apis","related":["/playbook/notifications/push-notifications/prune-push-subscriptions","/playbook/stack/product-services/fcm-push"],"sources":[{"label":"Apple APNs documentation, token-based connection","url":"https://developer.apple.com/documentation/usernotifications/establishing-a-token-based-connection-to-apns"},{"label":"Firebase documentation, FCM HTTP v1 API","url":"https://firebase.google.com/docs/cloud-messaging/send/v1-api"}]},{"chapter":"Push notifications","name":"Give every push a lifetime and a topic","level":"Recommended","rule":"Set how long each push may wait for an offline device. Give updates a topic, so a newer push replaces the older one.","check":"Turn a test device offline, send three updates with one topic and a TTL of an hour, and turn it on. One notification shows, with the last text. A push with a TTL of 60 seconds sent to a device that stays offline for 2 minutes never shows.","url":"https://agentwranglers.se/playbook/notifications/push-notifications/push-ttl-and-collapse","related":["/playbook/notifications/channels-and-preferences/one-message-per-event"],"sources":[{"label":"RFC 8030, HTTP Push, section 5.2 Push Message Time-To-Live","url":"https://www.rfc-editor.org/rfc/rfc8030#section-5.2"},{"label":"RFC 8030, HTTP Push, section 5.4 Replacing Push Messages","url":"https://www.rfc-editor.org/rfc/rfc8030#section-5.4"},{"label":"Apple APNs documentation, sending notification requests","url":"https://developer.apple.com/documentation/usernotifications/sending-notification-requests-to-apns"}]},{"chapter":"Push notifications","name":"Delete dead subscriptions and tokens at once","level":"Recommended","rule":"Delete a push subscription or device token as soon as the service says it is gone. Remove tokens the app has not refreshed for 270 days.","check":"Unsubscribe a staging browser and send to it. The row is gone after the send. A query finds no token with a last refresh older than 270 days.","url":"https://agentwranglers.se/playbook/notifications/push-notifications/prune-push-subscriptions","related":["/playbook/notifications/push-notifications/apns-fcm-current-apis","/playbook/api-design/reliability/retry-backoff","/playbook/stack/product-services/web-push-standard","/playbook/stack/product-services/fcm-push"],"sources":[{"label":"Firebase documentation, FCM registration token management","url":"https://firebase.google.com/docs/cloud-messaging/manage-tokens"},{"label":"RFC 8030, Generic Event Delivery Using HTTP Push","url":"https://www.rfc-editor.org/rfc/rfc8030"}]},{"chapter":"Push notifications","name":"Keep push payloads short and safe on a lock screen","level":"Required","risk":"data","rule":"Put no secrets, codes, health or money details in a push. Show a neutral line and the details only after sign-in. Keep the payload JSON at or under 3,993 bytes.","check":"A unit test renders every push template with sample data and fails if the text holds a sign-in code, an amount or a card number. It also fails if the JSON is over 3,993 bytes.","url":"https://agentwranglers.se/playbook/notifications/push-notifications/push-payload-privacy","related":["/playbook/email/email-privacy/email-data-minimisation"],"sources":[{"label":"RFC 8291, Message Encryption for Web Push, section 4","url":"https://www.rfc-editor.org/rfc/rfc8291#section-4"},{"label":"Apple App Review Guideline 4.5.4","url":"https://developer.apple.com/app-store/review/guidelines/#4.5.4"}]},{"chapter":"Push notifications","name":"Open the exact page the push is about","level":"Recommended","rule":"A tap on a notification opens the page it is about, reuses an open tab when there is one, and marks the item as read.","check":"A browser test receives a push, clicks it, and lands on `/orders/1042` in the open tab. The inbox item for that order is now read.","url":"https://agentwranglers.se/playbook/notifications/push-notifications/push-deep-links","related":["/playbook/website/standard-addresses/apple-app-site-association","/playbook/website/standard-addresses/assetlinks-json"],"sources":[{"label":"Service Workers, Clients openWindow method","url":"https://www.w3.org/TR/service-workers/#dom-clients-openwindow"},{"label":"Service Workers, WindowClient focus method","url":"https://www.w3.org/TR/service-workers/#dom-windowclient-focus"}]},{"chapter":"Push notifications","name":"Keep the app working when people say no","level":"Recommended","rule":"Every feature works when notification permission is denied or never asked. Nothing the person needs reaches them by push alone.","check":"A browser test denies notification permission and runs sign-up, checkout and the inbox flows. Each one completes, and a new notification shows in the inbox. No permission prompt appears a second time.","url":"https://agentwranglers.se/playbook/notifications/push-notifications/works-without-push","related":["/playbook/website/site-architecture/progressive-enhancement"],"sources":[{"label":"Apple App Review Guideline 4.5.4","url":"https://developer.apple.com/app-store/review/guidelines/#4.5.4"}]},{"chapter":"Push notifications","name":"Show a notification for every push","level":"Recommended","rule":"Every web push the service worker receives shows a notification before the handler ends. Never use push to sync data in the background without showing anything.","check":"A unit test runs the service worker's `push` handler with a normal payload, an empty payload and a broken payload. Each run calls `showNotification` once. A server test finds no high-priority FCM message without a `notification` block.","url":"https://agentwranglers.se/playbook/notifications/push-notifications/visible-push-only","related":["/playbook/notifications/in-app-notifications/notification-inbox"],"sources":[{"label":"W3C Push API","url":"https://www.w3.org/TR/push-api/"},{"label":"Apple, Sending web push notifications in web apps and browsers","url":"https://developer.apple.com/documentation/usernotifications/sending-web-push-notifications-in-web-apps-and-browsers"},{"label":"Firebase, Android message priority","url":"https://firebase.google.com/docs/cloud-messaging/android-message-priority"}]},{"chapter":"Push notifications","name":"Unlink push from the account at sign-out","level":"Required","risk":"data","applies":["accounts"],"rule":"When a session ends, delete the push subscriptions and device tokens it registered. The next person on that device never gets the last person's notifications.","check":"Sign in as user A in a test browser and turn on push. Sign out, sign in as user B, and send a notification to A. The push service gets no request for that endpoint, and no row links A to it.","url":"https://agentwranglers.se/playbook/notifications/push-notifications/unlink-push-on-sign-out","related":["/playbook/accounts/sessions-and-security-activity/revoke-sessions","/playbook/accounts/sessions-and-security-activity/owner-ends-sessions"],"sources":[{"label":"W3C Push API, PushSubscription unsubscribe method","url":"https://www.w3.org/TR/push-api/#dom-pushsubscription-unsubscribe"}]},{"chapter":"Push notifications","name":"Accept push endpoints only from known push services","level":"Required","risk":"takeover","rule":"Before you store a web push subscription, check that its endpoint is HTTPS on a push service host you allow. Refuse every other address.","check":"An API test posts subscriptions with the endpoints `https://169.254.169.254/latest`, `http://fcm.googleapis.com/fcm/send/x` and `https://push.example.com/x`. Each one gets 422, and no row is stored.","url":"https://agentwranglers.se/playbook/notifications/push-notifications/push-endpoint-allow-list","related":["/playbook/adversarial-testing/requests-and-files/block-server-side-request-forgery"],"sources":[{"label":"OWASP Server-Side Request Forgery Prevention Cheat Sheet","url":"https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html"},{"label":"Apple, Sending web push notifications in web apps and browsers","url":"https://developer.apple.com/documentation/usernotifications/sending-web-push-notifications-in-web-apps-and-browsers"}]},{"chapter":"Push notifications","name":"Send personal messages only to the person's devices","level":"Required","risk":"data","applies":["accounts"],"rule":"Send a broadcast topic only what anyone may read. Every message about one person goes to that person's own device tokens.","check":"A unit test runs every catalogue type through the send code. No personal type produces a topic or condition target. A grep finds no topic name built from a user, order or account ID.","url":"https://agentwranglers.se/playbook/notifications/push-notifications/personal-push-to-devices","related":["/playbook/email/email-privacy/email-data-minimisation"],"sources":[{"label":"Firebase, Topic messaging","url":"https://firebase.google.com/docs/cloud-messaging/topic-messaging"},{"label":"Firebase, Manage topic subscriptions","url":"https://firebase.google.com/docs/cloud-messaging/manage-topics"}]},{"chapter":"Push notifications","name":"Send the current subscription to the server on every start","level":"Recommended","rule":"Read the push subscription or device token each time the app starts, and send it to the server when it changed. Never trust a copy cached on the device.","check":"Unsubscribe the test browser's subscription by hand and reload the app. Within one start, the server holds the new endpoint and no row for the old one. Restore a test phone from a backup, and the server gets its new token at first launch.","url":"https://agentwranglers.se/playbook/notifications/push-notifications/refresh-push-subscriptions","related":["/playbook/data/transactions-and-concurrency/idempotent-writes"],"sources":[{"label":"W3C Push API, the pushsubscriptionchange event","url":"https://www.w3.org/TR/push-api/#the-pushsubscriptionchange-event"},{"label":"Apple, Registering your app with APNs","url":"https://developer.apple.com/documentation/usernotifications/registering-your-app-with-apns"},{"label":"Firebase, Manage FCM registrations","url":"https://firebase.google.com/docs/cloud-messaging/manage-tokens"}]},{"chapter":"Text messages","name":"Register every sender before you send","level":"Recommended","rule":"Send texts only from numbers or sender IDs registered for that country and use case. Never send business texts from an unregistered number.","check":"Every sending number and sender ID in the provider account maps to an approved registration. A staging send to each target country arrives with the expected sender.","url":"https://agentwranglers.se/playbook/notifications/text-messages/sms-sender-registration","related":["/playbook/notifications/text-messages/sms-opt-in-confirmation","/playbook/stack/product-services/twilio-sms"],"sources":[]},{"chapter":"Text messages","name":"Confirm each text opt-in","level":"Recommended","rule":"After an opt-in to recurring texts, send one confirmation. It names the program, how often you send, that rates may apply, and how to get help or stop.","check":"Opt in a test phone. The first text names the brand, the frequency, \"Msg and data rates may apply\", and both HELP and STOP. A template test fails if any of those words is missing.","url":"https://agentwranglers.se/playbook/notifications/text-messages/sms-opt-in-confirmation","related":["/playbook/notifications/text-messages/sms-sender-registration","/playbook/notifications/channels-and-preferences/marketing-consent","/playbook/email/deliverability/confirmed-opt-in"],"sources":[]},{"chapter":"Text messages","name":"Stop at the first STOP","level":"Required","risk":"law","rule":"Treat STOP and the other standard opt-out words as an instant opt-out for that sender. Answer HELP with who you are and how to reach you.","check":"Text \"stop \" and \"Opt out\" from a test phone. Each gets one confirmation within 5 minutes, and a send to that number is refused by your code. Text HELP and the answer names the brand and a support address.","url":"https://agentwranglers.se/playbook/notifications/text-messages/sms-stop-keywords","related":["/playbook/email/unsubscribe-and-bounces/honour-unsubscribes"],"sources":[{"label":"47 CFR 64.1200, Delivery restrictions","url":"https://www.ecfr.gov/current/title-47/chapter-I/subchapter-B/part-64/subpart-L/section-64.1200"},{"label":"FCC 26-67, consent revocation order of 30 September 2026","url":"https://docs.fcc.gov/public/attachments/FCC-26-67A1.pdf"}]},{"chapter":"Text messages","name":"Guard text sending against SMS pumping","level":"Required","risk":"money","rule":"Limit who can make you send a text, to which countries, and how often. SMS pumping fraud sends your codes to premium numbers and bills you.","check":"A test asks for a code to a number in a blocked country and expects a refusal. A script asks for 10 codes to one number in a minute and gets at most 3. An alert fires on a daily cost above budget.","url":"https://agentwranglers.se/playbook/notifications/text-messages/sms-pumping","related":["/playbook/adversarial-testing/abuse-and-limits/free-tier-abuse","/playbook/adversarial-testing/abuse-and-limits/limit-requests","/playbook/stack/product-services/twilio-sms"],"sources":[]},{"chapter":"Text messages","name":"Store phone numbers in E.164","level":"Recommended","rule":"Parse every phone number with a phone number library, and store it in E.164 format with its country.","check":"A database query finds no phone number that fails the pattern `^\\+[1-9][0-9]{6,14}$`. A unit test turns \"070-123 45 67\" with country SE into `+46701234567`.","url":"https://agentwranglers.se/playbook/notifications/text-messages/e164-phone-numbers","related":["/playbook/website/languages-and-regions/international-names-and-addresses","/playbook/architecture/data-shapes/parse-at-the-boundary"],"sources":[]},{"chapter":"Text messages","name":"Format code texts for automatic fill","level":"Recommended","applies":["accounts"],"rule":"End every sign-in code text with the origin-bound line `@example.com #123456`, using the exact host of the sign-in page.","check":"A template test asserts that the last line of every code text matches `^@example\\.com #[0-9]{4,8}$`.","url":"https://agentwranglers.se/playbook/notifications/text-messages/sms-code-autofill","related":["/playbook/website/accessibility/accessible-authentication","/playbook/notifications/text-messages/sms-segments"],"sources":[]},{"chapter":"Text messages","name":"Keep each text to one segment","level":"Recommended","rule":"Keep each text within one segment, and test every template for its encoding and length.","check":"A unit test renders every template with the longest sample data and counts segments. Each one is 1, or 2 where the catalogue allows it.","url":"https://agentwranglers.se/playbook/notifications/text-messages/sms-segments","related":["/playbook/notifications/text-messages/sms-code-autofill"],"sources":[]},{"chapter":"Text messages","name":"Track what happened to each text","level":"Recommended","rule":"Record the provider's delivery status for every text through a signed callback, and stop sending to numbers that keep failing.","check":"Send to a provider test number that always fails. The send row shows the failure code, and after the third failure the number is suppressed.","url":"https://agentwranglers.se/playbook/notifications/text-messages/sms-delivery-status","related":["/playbook/api-design/webhooks-and-events/signed-webhooks","/playbook/notifications/text-messages/reassigned-numbers"],"sources":[]},{"chapter":"Text messages","name":"Check that a number still belongs to the person","level":"Recommended","rule":"Before you text a number whose consent is older than the last check, confirm the number has not passed to someone else. Suppress it when it has.","check":"A test feeds the send job a US number that the database stub marks as reassigned. No text is sent, and the number is suppressed. A query finds no US text sent without a database check dated after the consent.","url":"https://agentwranglers.se/playbook/notifications/text-messages/reassigned-numbers","related":["/playbook/notifications/text-messages/sms-delivery-status","/playbook/email/email-privacy/consent-records"],"sources":[{"label":"47 CFR 64.1200, Delivery restrictions","url":"https://www.ecfr.gov/current/title-47/chapter-I/subchapter-B/part-64/subpart-L/section-64.1200"}]},{"chapter":"In-app notifications","name":"Store every notification in a server-side inbox","level":"Recommended","rule":"Write every notification to an inbox table on the server first, and let push, text and email point to that row. The browser never holds the only copy.","check":"An API test signs in as user A and asks for user B's notification by ID. It gets 404. A second test sends a push and finds the matching inbox row by its ID.","url":"https://agentwranglers.se/playbook/notifications/in-app-notifications/notification-inbox","related":["/playbook/notifications/push-notifications/visible-push-only","/playbook/adversarial-testing/access-and-accounts/object-access","/playbook/api-design/lists/cursor-pagination"],"sources":[]},{"chapter":"In-app notifications","name":"Keep read state the same everywhere","level":"Recommended","rule":"Mark an item read once on the server, and clear it in every open tab, every device badge and any pending reminder.","check":"Open the inbox in two tabs and read an item in one. The other tab's unread count changes within 2 seconds. A pending reminder email for that item is not sent.","url":"https://agentwranglers.se/playbook/notifications/in-app-notifications/read-state-sync","related":["/playbook/notifications/channels-and-preferences/one-message-per-event","/playbook/app-ui/client-and-server-state/refresh-after-change"],"sources":[]},{"chapter":"In-app notifications","name":"Deliver new items live and catch up after a gap","level":"Recommended","rule":"Push new inbox items to open pages over one live connection, and fetch what was missed after every reconnect.","check":"Open the inbox, cut the network for 30 seconds while two notifications are created, then restore it. Both appear once, in order, without a reload. A test that creates an item during the replay query sees it exactly once.","url":"https://agentwranglers.se/playbook/notifications/in-app-notifications/live-inbox-updates","related":["/playbook/adversarial-testing/requests-and-files/websocket-origin"],"sources":[]},{"chapter":"In-app notifications","name":"Announce new items without stealing focus","level":"Recommended","rule":"Announce new notifications to screen readers through a polite live region, and never move focus or open a dialog for them.","check":"Run axe-core on the page with the inbox open and closed, and get zero violations. A browser test creates a notification and finds its text in the `role=\"status\"` element, while `document.activeElement` stays where it was.","url":"https://agentwranglers.se/playbook/notifications/in-app-notifications/accessible-inbox","related":["/playbook/website/accessibility/status-messages","/playbook/website/accessibility/time-limits"],"sources":[{"label":"WCAG 2.2, 4.1.3 Status Messages","url":"https://www.w3.org/TR/WCAG22/#status-messages"},{"label":"WAI-ARIA 1.2, aria-live","url":"https://www.w3.org/TR/wai-aria-1.2/#aria-live"}]},{"chapter":"In-app notifications","name":"Delete old inbox items on a schedule","level":"Recommended","rule":"Delete inbox items after a fixed time, such as 90 days, and show the limit in the inbox.","check":"Insert a test row dated 91 days ago and run the job. The row is gone. A query finds no notification older than 91 days, which allows for one day between runs.","url":"https://agentwranglers.se/playbook/notifications/in-app-notifications/inbox-retention","related":["/playbook/data/personal-data/retention-jobs"],"sources":[{"label":"GDPR Article 5, Principles of processing","url":"https://eur-lex.europa.eu/eli/reg/2016/679/oj"}]}]}