vybekiit 0.7.3 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +16607 -7527
- package/dist/global-skills/aws-cdk/SKILL.md +2 -1
- package/dist/global-skills/aws-cloudformation/SKILL.md +2 -1
- package/dist/global-skills/aws-serverless/SKILL.md +45 -44
- package/dist/global-skills/aws-serverless/assets/powertools-handler.py +2 -1
- package/dist/global-skills/aws-serverless/references/api-gateway.md +50 -470
- package/dist/global-skills/aws-serverless/references/architecture.md +47 -186
- package/dist/global-skills/aws-serverless/references/concurrency.md +44 -158
- package/dist/global-skills/aws-serverless/references/deployment.md +1 -1
- package/dist/global-skills/aws-serverless/references/event-sources.md +72 -391
- package/dist/global-skills/aws-serverless/references/lambda.md +69 -428
- package/dist/global-skills/aws-serverless/references/orchestration.md +65 -384
- package/dist/global-skills/aws-serverless/references/production.md +78 -415
- package/dist/global-skills/aws-serverless/references/troubleshooting.md +92 -619
- package/dist/global-skills/claimable-postgres/SKILL.md +60 -49
- package/dist/global-skills/design-my-data/SKILL.md +2 -0
- package/dist/global-skills/eas-app-stores/SKILL.md +8 -0
- package/dist/global-skills/eas-app-stores/references/play-store.md +2 -0
- package/dist/global-skills/eas-hosting/SKILL.md +8 -0
- package/dist/global-skills/eas-observe/SKILL.md +30 -8
- package/dist/global-skills/eas-observe/agents/openai.yaml +1 -1
- package/dist/global-skills/eas-observe/references/metrics.md +54 -3
- package/dist/global-skills/eas-observe/references/queries.md +78 -17
- package/dist/global-skills/eas-observe/references/setup.md +164 -5
- package/dist/global-skills/eas-observe/references/third-party.md +136 -0
- package/dist/global-skills/eas-simulator/SKILL.md +63 -11
- package/dist/global-skills/eas-simulator/references/controllers.md +21 -2
- package/dist/global-skills/eas-simulator/references/run-your-app.md +16 -11
- package/dist/global-skills/eas-simulator/references/troubleshooting.md +8 -5
- package/dist/global-skills/eas-update-insights/SKILL.md +8 -0
- package/dist/global-skills/eas-workflows/SKILL.md +15 -10
- package/dist/global-skills/eas-workflows/scripts/package.json +2 -7
- package/dist/global-skills/email-best-practices/.github/workflows/sync-skills.yml +30 -0
- package/dist/global-skills/email-best-practices/README.md +63 -0
- package/dist/global-skills/email-best-practices/references/accessibility.md +189 -0
- package/dist/global-skills/email-best-practices/references/compliance.md +125 -0
- package/dist/global-skills/email-best-practices/references/deliverability.md +121 -0
- package/dist/global-skills/email-best-practices/references/email-capture.md +129 -0
- package/dist/global-skills/email-best-practices/references/email-types.md +173 -0
- package/dist/global-skills/email-best-practices/references/list-management.md +157 -0
- package/dist/global-skills/email-best-practices/references/marketing-emails.md +115 -0
- package/dist/global-skills/email-best-practices/references/sending-reliability.md +155 -0
- package/dist/global-skills/email-best-practices/references/transactional-email-catalog.md +418 -0
- package/dist/global-skills/email-best-practices/references/transactional-emails.md +92 -0
- package/dist/global-skills/email-best-practices/references/webhooks-events.md +167 -0
- package/dist/global-skills/email-best-practices/tests/README.md +35 -0
- package/dist/global-skills/email-best-practices/tests/scenarios/01-spam-deliverability.md +46 -0
- package/dist/global-skills/email-best-practices/tests/scenarios/02-multi-region-compliance.md +48 -0
- package/dist/global-skills/email-best-practices/tests/scenarios/03-retry-idempotency.md +36 -0
- package/dist/global-skills/email-best-practices/tests/scenarios/04-webhook-bounce-handling.md +52 -0
- package/dist/global-skills/email-best-practices/tests/scenarios/05-new-saas-email-plan.md +51 -0
- package/dist/global-skills/expo-animation/LICENSE +21 -0
- package/dist/global-skills/expo-animation/RECIPES.md +385 -0
- package/dist/global-skills/expo-animation/SKILL.md +267 -0
- package/dist/global-skills/expo-animation/agents/openai.yaml +4 -0
- package/dist/global-skills/expo-app-clip/SKILL.md +8 -0
- package/dist/global-skills/expo-brownfield/SKILL.md +8 -0
- package/dist/global-skills/expo-brownfield/references/brownfield-isolated.md +53 -4
- package/dist/global-skills/expo-data-fetching/SKILL.md +8 -0
- package/dist/global-skills/expo-design-system/SKILL.md +356 -0
- package/dist/global-skills/expo-design-system/agents/openai.yaml +4 -0
- package/dist/global-skills/expo-design-system/references/audit.md +185 -0
- package/dist/global-skills/expo-dev-client/SKILL.md +8 -0
- package/dist/global-skills/expo-dom/SKILL.md +8 -0
- package/dist/global-skills/expo-examples/SKILL.md +8 -0
- package/dist/global-skills/expo-migrate-module/SKILL.md +113 -0
- package/dist/global-skills/expo-migrate-module/agents/openai.yaml +4 -0
- package/dist/global-skills/expo-migrate-module/references/compatibility.md +73 -0
- package/dist/global-skills/expo-migrate-module/references/example.md +212 -0
- package/dist/global-skills/expo-migrate-module/references/migration-map.md +306 -0
- package/dist/global-skills/expo-module/SKILL.md +11 -1
- package/dist/global-skills/expo-native-ui/SKILL.md +8 -0
- package/dist/global-skills/expo-project-structure/SKILL.md +114 -0
- package/dist/global-skills/expo-project-structure/agents/openai.yaml +4 -0
- package/dist/global-skills/expo-router/SKILL.md +8 -0
- package/dist/global-skills/expo-skill-feedback/SKILL.md +66 -33
- package/dist/global-skills/expo-skill-feedback/agents/openai.yaml +2 -2
- package/dist/global-skills/expo-tailwind-setup/SKILL.md +8 -0
- package/dist/global-skills/expo-ui/SKILL.md +8 -0
- package/dist/global-skills/expo-upgrade/SKILL.md +15 -1
- package/dist/global-skills/expo-web-to-native/SKILL.md +8 -0
- package/dist/global-skills/extension-to-functions-codebase/SKILL.md +129 -0
- package/dist/global-skills/extension-to-functions-codebase/references/configuration-migration.md +161 -0
- package/dist/global-skills/extension-to-functions-codebase/references/destructuring-shim.md +122 -0
- package/dist/global-skills/extension-to-functions-codebase/references/signature-mapping.md +84 -0
- package/dist/global-skills/firebase-ai-logic-basics/SKILL.md +61 -19
- package/dist/global-skills/firebase-ai-logic-basics/references/flutter_setup.md +6 -3
- package/dist/global-skills/firebase-app-hosting-basics/SKILL.md +5 -1
- package/dist/global-skills/firebase-auth-basics/SKILL.md +4 -2
- package/dist/global-skills/firebase-basics/SKILL.md +5 -5
- package/dist/global-skills/firebase-crashlytics/SKILL.md +2 -0
- package/dist/global-skills/firebase-data-connect/SKILL.md +2 -0
- package/dist/global-skills/firebase-firestore/SKILL.md +3 -6
- package/dist/global-skills/firebase-hosting-basics/SKILL.md +4 -1
- package/dist/global-skills/firebase-remote-config-basics/SKILL.md +5 -1
- package/dist/global-skills/firebase-security-rules-auditor/SKILL.md +4 -1
- package/dist/global-skills/harden/SKILL.md +2 -1
- package/dist/global-skills/instrument-feature-flags/SKILL.md +1 -0
- package/dist/global-skills/instrument-feature-flags/references/COMMANDMENTS.md +5 -0
- package/dist/global-skills/instrument-feature-flags/references/adding-feature-flag-code.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/android.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/api.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/best-practices.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/django.md +38 -5
- package/dist/global-skills/instrument-feature-flags/references/dotnet.md +10 -4
- package/dist/global-skills/instrument-feature-flags/references/elixir.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/flask.md +17 -5
- package/dist/global-skills/instrument-feature-flags/references/flutter.md +7 -5
- package/dist/global-skills/instrument-feature-flags/references/go.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/ios.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/java.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/laravel.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/next-js.md +74 -8
- package/dist/global-skills/instrument-feature-flags/references/nodejs.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/php.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/python.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/react-native.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/react.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/ruby-on-rails.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/ruby.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/rust.md +6 -4
- package/dist/global-skills/instrument-feature-flags/references/usage.md +41 -4
- package/dist/global-skills/instrument-feature-flags/references/web.md +7 -5
- package/dist/global-skills/instrument-product-analytics/SKILL.md +2 -1
- package/dist/global-skills/instrument-product-analytics/references/COMMANDMENTS.md +5 -0
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-hybrid.md +12 -14
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-ssr.md +25 -21
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-django.md +75 -55
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-fastapi.md +3 -6
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-flask.md +26 -16
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-nuxt-3-6.md +8 -5
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-nuxt-4.md +4 -4
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-react-react-router-7-framework.md +3 -3
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-ruby-on-rails.md +8 -4
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-tanstack-start.md +20 -8
- package/dist/global-skills/instrument-product-analytics/references/android.md +46 -4
- package/dist/global-skills/instrument-product-analytics/references/angular.md +41 -5
- package/dist/global-skills/instrument-product-analytics/references/astro.md +41 -5
- package/dist/global-skills/instrument-product-analytics/references/configuration.md +7 -4
- package/dist/global-skills/instrument-product-analytics/references/django.md +38 -5
- package/dist/global-skills/instrument-product-analytics/references/dotnet.md +10 -4
- package/dist/global-skills/instrument-product-analytics/references/elixir.md +6 -4
- package/dist/global-skills/instrument-product-analytics/references/flask.md +17 -5
- package/dist/global-skills/instrument-product-analytics/references/flutter.md +49 -4
- package/dist/global-skills/instrument-product-analytics/references/go.md +6 -4
- package/dist/global-skills/instrument-product-analytics/references/identify-users.md +38 -7
- package/dist/global-skills/instrument-product-analytics/references/ios.md +14 -4
- package/dist/global-skills/instrument-product-analytics/references/laravel.md +6 -4
- package/dist/global-skills/instrument-product-analytics/references/next-js.md +74 -8
- package/dist/global-skills/instrument-product-analytics/references/nuxt-js-3-6.md +39 -7
- package/dist/global-skills/instrument-product-analytics/references/nuxt-js.md +40 -4
- package/dist/global-skills/instrument-product-analytics/references/php.md +6 -4
- package/dist/global-skills/instrument-product-analytics/references/posthog-python.md +21 -17
- package/dist/global-skills/instrument-product-analytics/references/python.md +15 -5
- package/dist/global-skills/instrument-product-analytics/references/react-native.md +41 -7
- package/dist/global-skills/instrument-product-analytics/references/react-router-v6.md +18 -4
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-data-mode.md +18 -4
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md +18 -4
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-framework-mode.md +18 -4
- package/dist/global-skills/instrument-product-analytics/references/ruby-on-rails.md +6 -4
- package/dist/global-skills/instrument-product-analytics/references/ruby.md +6 -4
- package/dist/global-skills/instrument-product-analytics/references/svelte.md +42 -6
- package/dist/global-skills/instrument-product-analytics/references/tanstack-start.md +31 -7
- package/dist/global-skills/instrument-product-analytics/references/usage.md +41 -4
- package/dist/global-skills/instrument-product-analytics/references/vue-js.md +42 -6
- package/dist/global-skills/manifest.json +6 -1
- package/dist/global-skills/neon/SKILL.md +235 -121
- package/dist/global-skills/neon-ai-gateway/SKILL.md +95 -16
- package/dist/global-skills/neon-functions/SKILL.md +242 -152
- package/dist/global-skills/neon-functions/references/ai-sdk.md +9 -3
- package/dist/global-skills/neon-functions/references/mastra-studio.md +1 -1
- package/dist/global-skills/neon-functions/references/mcp.md +13 -4
- package/dist/global-skills/neon-functions/references/sentry.md +165 -52
- package/dist/global-skills/neon-functions/references/sse.md +23 -6
- package/dist/global-skills/neon-object-storage/SKILL.md +40 -16
- package/dist/global-skills/neon-postgres/SKILL.md +63 -262
- package/dist/global-skills/neon-postgres-branches/SKILL.md +61 -50
- package/dist/global-skills/neon-postgres-egress-optimizer/SKILL.md +22 -3
- package/dist/global-skills/resend/SKILL.md +6 -6
- package/dist/global-skills/resend/references/automations.md +2 -2
- package/dist/global-skills/resend/references/broadcasts.md +11 -3
- package/dist/global-skills/resend/references/domains.md +25 -3
- package/dist/global-skills/resend/references/installation.md +7 -7
- package/dist/global-skills/setup-payments/SKILL.md +4 -5
- package/dist/global-skills/stripe-best-practices/SKILL.md +32 -7
- package/dist/global-skills/stripe-best-practices/references/billing.md +11 -1
- package/dist/global-skills/stripe-best-practices/references/payments.md +23 -2
- package/dist/global-skills/stripe-best-practices/references/security.md +21 -11
- package/dist/global-skills/stripe-best-practices/references/tax.md +116 -11
- package/dist/global-skills/supabase/CHANGELOG.md +25 -0
- package/dist/global-skills/supabase/SKILL.md +5 -1
- package/dist/global-skills/supabase-postgres-best-practices/CHANGELOG.md +30 -0
- package/dist/global-skills/supabase-postgres-best-practices/SKILL.md +1 -1
- package/dist/global-skills/use-kit-mcp/SKILL.md +5 -5
- package/dist/global-skills/use-railway/SKILL.md +4 -4
- package/dist/global-skills/use-railway/references/iac.md +206 -57
- package/dist/global-skills/use-railway/scripts/railway-api.sh +7 -2
- package/dist/global-skills/xcode-project-setup/SKILL.md +2 -0
- package/package.json +12 -9
- package/LICENSE.md +0 -47
- package/dist/global-skills/eas-workflows/scripts/validate.js +0 -84
- package/dist/global-skills/expo-skill-feedback/scripts/skill-feedback.cjs +0 -118
- package/dist/global-skills/neon-functions/references/hono-websockets.md +0 -145
|
@@ -4,15 +4,17 @@ Concrete errors seen while validating this flow, and the fix.
|
|
|
4
4
|
|
|
5
5
|
| Symptom | Cause | Fix |
|
|
6
6
|
|---|---|---|
|
|
7
|
-
| `Command simulator:start not found` | `eas-cli` too old (commands are hidden but present from ≥ 20.3.0) | Run via `npx eas-cli@latest …`, or upgrade `eas-cli`. |
|
|
7
|
+
| `Command simulator:start not found` | `eas-cli` too old (commands are hidden but present from ≥ 20.3.0) | Run via `npx --yes eas-cli@latest …`, or upgrade `eas-cli`. |
|
|
8
|
+
| `simulator:start` rejects `--name` (e.g. `Nonexistent flag: --name`) | `eas-cli` too old — `--name` was added after `simulator:start` itself | Run via `npx --yes eas-cli@latest …`, or upgrade `eas-cli`. If you can't upgrade, retry once **without** `--name`; the session starts unnamed. |
|
|
8
9
|
| `An Expo user account is required` / `whoami` shows logged-out | No browser login on a cloud/CI/headless box, or `EXPO_TOKEN` unset/invalid | Set **`EXPO_TOKEN`** (expo.dev → Account → Access Tokens) in the env; verify `npx --yes eas-cli@latest whoami`. (Interactive machines can `eas login`.) |
|
|
9
10
|
| `simulator:start`/`build`: no linked project / missing `projectId` | A fresh `create-expo-app` isn't linked to EAS | `npx --yes eas-cli@latest init` to create/link it (writes `extra.eas.projectId`). |
|
|
10
11
|
| `prebuild`/`eas build` prompts for or fails on a missing **iOS bundle identifier** | A fresh app often has no `ios.bundleIdentifier` | Set it in app config (e.g. `dev.<owner>.<slug>`); confirm via `npx expo config --json` (may live in `app.config.js`). |
|
|
11
|
-
| `--max-duration-minutes` rejected
|
|
12
|
+
| `--max-duration-minutes` rejected | The flag is **paid-plan only** | Drop `--max-duration-minutes` to use the default. |
|
|
13
|
+
| `simulator:start` fails with `not enabled for this account` / not-allowlisted | EAS Simulator is limited-access and isn't enabled for this account | Don't retry. Confirm with `simulator:availability`, then hand off gracefully — tell the user and fall back to a local sim / EAS Build (see SKILL.md *Check availability first*). |
|
|
12
14
|
| `start` keeps "Waiting for … session to be ready" but it never returns | `start`'s readiness poll can miss a session that's actually live | Don't rely on it — poll `npx --yes eas-cli@latest simulator:get --id <id> --json` for `status: IN_PROGRESS` + a populated `remoteConfig`. |
|
|
13
15
|
| `ERR_NGROK_3200` / endpoint offline; `Remote daemon is unavailable` | The session's tunnel/daemon dropped — left idle and timed out, or the VM was torn down | A drop invalidates the **whole** session (installed app, `@e` refs, Metro). **Don't retry the failed verb** — start a fresh session, reset the dotenv, and re-run install→open→drive from the top, acting immediately. |
|
|
14
|
-
| Two sessions running / orphaned session / surprise double billing | A second `start` (e.g. to "retry" a slow boot) creates a second billed session and overwrites the dotenv id, orphaning the first | Never `start` again to retry — poll the existing session instead. Find orphans with `simulator:list --status
|
|
15
|
-
| A device verb hangs (no return for a minute+) | Slow daemon; `press`/`screenshot` can block ~90s |
|
|
16
|
+
| Two sessions running / orphaned session / surprise double billing | A second `start` (e.g. to "retry" a slow boot) creates a second billed session and overwrites the dotenv id, orphaning the first | Never `start` again to retry — poll the existing session instead. Find orphans with `simulator:list --status in-progress` and stop each with `simulator:stop --id <id>`. |
|
|
17
|
+
| A device verb hangs (no return for a minute+) | Slow daemon; `press`/`screenshot` can block ~90s | Bound it with agent-device's own `--timeout <ms>` (e.g. `--timeout 120000`) — **not** a shell `timeout` wrapper (macOS has no `timeout` binary, so `timeout 120 …` fails with `command not found` and skips the verb). On timeout `snapshot -i` to see if the action landed before retrying (taps can double-fire). Don't blind-retry. |
|
|
16
18
|
| `install requires an active session or an explicit device selector` | `install` can't infer the device | Pass `--platform ios` (or `open` something first to establish a session). |
|
|
17
19
|
| `Unknown command: tap` | The tap verb is `press` | Use `press <ref\|selector>` (e.g. `press @e2` or `press 'label="Open"'`). |
|
|
18
20
|
| `SESSION_NOT_FOUND: No active session. Run open first.` | A verb (e.g. `screenshot`) ran before any app/session was opened | `open <app\|url>` first (or pass `--platform ios`). |
|
|
@@ -23,12 +25,13 @@ Concrete errors seen while validating this flow, and the fix.
|
|
|
23
25
|
| (Mode C) Deep-link `open` lands on the dev-client launcher, not the app | Opening the deep link triggers a system "Open in '<app>'?" dialog; and the launcher only auto-discovers Metro on the LAN | `press 'label="Open"'` to dismiss the dialog, then "Enter URL manually" → `fill` the `https://<host>.on.expo.app` manifest URL → "Connect". |
|
|
24
26
|
| (Mode C) App shows expo-router "Unmatched Route" | The connect URL was parsed as a route path | `press 'label="Go back"'` (or navigate to `/`). |
|
|
25
27
|
| (Mode C) Dev client shows a `?` placeholder / blank after connect | Bundle not fetched yet | `press 'label="Reload"'` and wait ~40-60s for the first build+transfer over the tunnel. |
|
|
26
|
-
| (Mode C) `expo start` fails: "port 8081 already in use" |
|
|
28
|
+
| (Mode C) `expo start` fails: "port 8081 already in use" | Something already owns that port (often another Metro) | Don't kill it — start on another port (`--port <N>`, e.g. 8082). Each `expo start --tunnel` gets its own tunnel URL, so ports coexist. Only reuse a running Metro if you started it this session. |
|
|
27
29
|
| (Mode C) `expo start` / `node` killed with **exit 137** | 137 = SIGKILL — almost always the **OOM killer** (memory pressure, common in constrained cloud sandboxes, esp. a native build + Metro at once). **Not** a port clash. | Reduce memory pressure: don't run a native build and Metro concurrently; give the sandbox more memory; retry. |
|
|
28
30
|
| (Mode C) Edits won't live-reload no matter how often you reconnect | A **release** build is installed — its JS is baked in, so it ignores Metro | Stop reconnecting: **install the dev (Debug) build**, connect it to Metro, reload. Reconnecting a release build to Metro is a no-op. |
|
|
29
31
|
| `expo start --tunnel` errors for a robot/`EXPO_TOKEN` user | The ngrok robot-user guard | Use tunnel v2: `EXPO_UNSTABLE_TUNNEL_V2=1 expo start --tunnel`. |
|
|
30
32
|
| Unexpected charges / a session you forgot | `start --non-interactive` does NOT auto-stop | Always `npx --yes eas-cli@latest simulator:stop --id <id>`. List leftovers with `npx --yes eas-cli@latest simulator:list`. |
|
|
31
33
|
| Screenshot shows **old content** / my recent edits don't appear | Running a **release build (Mode A/B)** whose JS was baked in *before* your edits — typically a reused/stale build | A/B reflect code at build time, not now. **Rebuild** (ensure the build's fingerprint matches current source), or use **Mode C** (dev + Metro) so live edits show via Fast Refresh. The screenshot itself is fresh — it's the build that's stale. (`9:41` in the status bar is the sim default, not staleness.) |
|
|
34
|
+
| (argent) Every `argent run`/`tools` call returns `401 Unauthorized` right after linking | `argent link` without `--yes` no-ops on an already-linked URL ("Already linked. No changes."), keeping a stale token from a previous session | Re-link with `--yes` so the new token is written — see the link command in [controllers.md](./controllers.md). |
|
|
32
35
|
|
|
33
36
|
## Performance expectations
|
|
34
37
|
|
|
@@ -228,3 +228,11 @@ Human-readable group details plus 30 days of launches/failures per platform —
|
|
|
228
228
|
- **Fresh publishes** may show zeros for a short period while the metrics pipeline catches up.
|
|
229
229
|
- **Installs are downloads, not launches**: the `installs` / "Launches" field counts users who downloaded the manifest and launch asset. A confirmed run only registers on the user's *next* update check (typically up to 24h later, depending on the app's update policy). So metrics lag the real-world state slightly.
|
|
230
230
|
- **Crashes are self-reported**: `failedInstalls` / "Crashes" counts updates that errored during install/launch and were reported on the next update check. Crashes that don't trigger an update request (e.g. process kill before recovery) won't appear.
|
|
231
|
+
|
|
232
|
+
## Submitting Feedback
|
|
233
|
+
If you encounter errors, misleading or outdated information in this skill, report it so Expo can improve:
|
|
234
|
+
```bash
|
|
235
|
+
npx --yes submit-expo-feedback@latest --category skills --subject "eas-update-insights" "<actionable feedback>"
|
|
236
|
+
```
|
|
237
|
+
Only submit when you have something specific and actionable to report. Include as much relevant context as possible.
|
|
238
|
+
If an AI agent repeatedly failed or the user had to take over an Expo task, load the expo-skill-feedback skill and follow its eval-candidate flow instead of reusing the command above.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eas-workflows
|
|
3
3
|
description: EAS service (paid). Helps understand and write EAS workflow YAML files for Expo projects. Use this skill when the user asks about CI/CD or workflows in an Expo or EAS context, mentions .eas/workflows/, or wants help with EAS build pipelines or deployment automation.
|
|
4
|
-
allowed-tools: "Read,Write,Bash(node:*)"
|
|
4
|
+
allowed-tools: "Read,Write,Bash(node:*),Bash(npx *eas-cli@*)"
|
|
5
5
|
version: 1.0.0
|
|
6
6
|
license: MIT License
|
|
7
7
|
---
|
|
@@ -14,7 +14,7 @@ Help developers write and edit EAS CI/CD workflow YAML files.
|
|
|
14
14
|
|
|
15
15
|
## Reference Documentation
|
|
16
16
|
|
|
17
|
-
Fetch these resources before generating or
|
|
17
|
+
Fetch these resources before generating or editing workflow files, or when answering syntax questions. First resolve this skill's directory, then use the fetch script in its `scripts/` directory. It is implemented using Node.js and caches responses using ETags for efficiency:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
# Fetch resources
|
|
@@ -23,7 +23,7 @@ node <skill-dir>/scripts/fetch.js <url>
|
|
|
23
23
|
|
|
24
24
|
1. **JSON Schema** — https://api.expo.dev/v2/workflows/schema
|
|
25
25
|
- It is NECESSARY to fetch this schema
|
|
26
|
-
- Source of truth for
|
|
26
|
+
- Source of truth for the workflow YAML structure; EAS CLI remains the authoritative final validator
|
|
27
27
|
- All job types and their required/optional parameters
|
|
28
28
|
- Trigger types and configurations
|
|
29
29
|
- Runner types, VM images, and all enums
|
|
@@ -41,7 +41,7 @@ Do not rely on memorized values; these resources evolve as new features are adde
|
|
|
41
41
|
|
|
42
42
|
## Workflow File Location
|
|
43
43
|
|
|
44
|
-
Workflows live in `.eas/workflows/*.yml` (or `.yaml`).
|
|
44
|
+
Workflows live in `.eas/workflows/*.yml` (or `.yaml`). Each file must be 16 KiB or smaller.
|
|
45
45
|
|
|
46
46
|
## Top-Level Structure
|
|
47
47
|
|
|
@@ -78,17 +78,22 @@ When generating or editing workflows:
|
|
|
78
78
|
|
|
79
79
|
## Validation
|
|
80
80
|
|
|
81
|
-
After generating or editing a workflow file, validate it
|
|
81
|
+
After generating or editing a workflow file, validate it with EAS CLI from the Expo project root:
|
|
82
82
|
|
|
83
83
|
```sh
|
|
84
|
-
|
|
85
|
-
[ -d "<skill-dir>/scripts/node_modules" ] || npm install --prefix <skill-dir>/scripts
|
|
86
|
-
|
|
87
|
-
node <skill-dir>/scripts/validate.js <workflow.yml> [workflow2.yml ...]
|
|
84
|
+
npx -y eas-cli@latest workflow:validate .eas/workflows/<workflow.yml> --non-interactive
|
|
88
85
|
```
|
|
89
86
|
|
|
90
|
-
|
|
87
|
+
Run the command separately for each changed workflow file. It requires a logged-in EAS CLI session and a linked Expo project. Unlike schema-only validation, it also checks build profile references against the project's `eas.json` and performs EAS server-side validation. Fix every reported error and rerun the command until it prints `Workflow configuration YAML is valid.` Do not replace this command with a local YAML or JSON Schema validator.
|
|
91
88
|
|
|
92
89
|
## Answering Questions
|
|
93
90
|
|
|
94
91
|
When users ask about available options (job types, triggers, runner types, etc.), fetch the schema and derive the answer from it rather than relying on potentially outdated information.
|
|
92
|
+
|
|
93
|
+
## Submitting Feedback
|
|
94
|
+
If you encounter errors, misleading or outdated information in this skill, report it so Expo can improve:
|
|
95
|
+
```bash
|
|
96
|
+
npx --yes submit-expo-feedback@latest --category skills --subject "eas-workflows" "<actionable feedback>"
|
|
97
|
+
```
|
|
98
|
+
Only submit when you have something specific and actionable to report. Include as much relevant context as possible.
|
|
99
|
+
If an AI agent repeatedly failed or the user had to take over an Expo task, load the expo-skill-feedback skill and follow its eval-candidate flow instead of reusing the command above.
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "@expo/
|
|
2
|
+
"name": "@expo/eas-workflows-skill",
|
|
3
3
|
"version": "0.0.0",
|
|
4
4
|
"private": true,
|
|
5
|
-
"type": "module"
|
|
6
|
-
"dependencies": {
|
|
7
|
-
"ajv": "^8.17.1",
|
|
8
|
-
"ajv-formats": "^3.0.1",
|
|
9
|
-
"js-yaml": "^4.1.0"
|
|
10
|
-
}
|
|
5
|
+
"type": "module"
|
|
11
6
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Sync Skills
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- 'SKILL.md'
|
|
8
|
+
- 'references/**'
|
|
9
|
+
- 'scripts/**'
|
|
10
|
+
- 'assets/**'
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
sync:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- name: Trigger sync on resend-skills
|
|
21
|
+
env:
|
|
22
|
+
GH_TOKEN: ${{ secrets.SYNC_SKILLS_TO_RESEND_SKILLS }}
|
|
23
|
+
run: |
|
|
24
|
+
gh workflow run sync-from-repo.yml \
|
|
25
|
+
--repo resend/resend-skills \
|
|
26
|
+
--field repo=email-best-practices \
|
|
27
|
+
--field skill-path=. \
|
|
28
|
+
--field skill-name=email-best-practices \
|
|
29
|
+
--field sha=${{ github.sha }} \
|
|
30
|
+
--field reviewer=${{ github.actor }}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
```
|
|
2
|
+
╔══════════════════════════════════════╗
|
|
3
|
+
║ _____ __ __ _ ___ _ ║
|
|
4
|
+
║ | ____| \/ | / \ |_ _| | ║
|
|
5
|
+
║ | _| | |\/| | / _ \ | || | ║
|
|
6
|
+
║ | |___| | | |/ ___ \ | || |___ ║
|
|
7
|
+
║ |_____|_| |_/_/ \_\___|_____| ║
|
|
8
|
+
║ ║
|
|
9
|
+
║ Best Practices ║
|
|
10
|
+
╚══════════════════════════════════════╝
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
# Email Best Practices Skill
|
|
14
|
+
|
|
15
|
+
A comprehensive agent skill for building production-ready email systems. Covers everything from DNS authentication to webhook processing, with a focus on deliverability, compliance, and reliability.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx skills add resend/email-best-practices
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## What This Skill Covers
|
|
24
|
+
|
|
25
|
+
**Getting Started**
|
|
26
|
+
- Planning which emails your app needs (password reset, verification, order confirmations)
|
|
27
|
+
- Setting up email authentication (SPF, DKIM, DMARC) so emails reach inboxes
|
|
28
|
+
|
|
29
|
+
**Sending Emails**
|
|
30
|
+
- Transactional email design (subject lines, content structure, mobile-first)
|
|
31
|
+
- Marketing email best practices (consent, segmentation, unsubscribe)
|
|
32
|
+
- Compliance requirements by region (CAN-SPAM, GDPR, CASL)
|
|
33
|
+
|
|
34
|
+
**Production Infrastructure**
|
|
35
|
+
- Idempotency and retry logic to prevent duplicates
|
|
36
|
+
- Webhook processing for delivery events
|
|
37
|
+
- Suppression lists and list hygiene automation
|
|
38
|
+
|
|
39
|
+
## Structure
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
email-best-practices/
|
|
43
|
+
├── SKILL.md # Start here - routes to the right resource
|
|
44
|
+
└── references/
|
|
45
|
+
├── deliverability.md # SPF/DKIM/DMARC, sender reputation
|
|
46
|
+
├── transactional-emails.md # Password resets, OTPs, confirmations
|
|
47
|
+
├── transactional-email-catalog.md # Email combinations by app type
|
|
48
|
+
├── marketing-emails.md # Newsletters, campaigns, consent
|
|
49
|
+
├── email-capture.md # Validation, verification, opt-in
|
|
50
|
+
├── compliance.md # CAN-SPAM, GDPR, CASL
|
|
51
|
+
├── email-types.md # Transactional vs marketing
|
|
52
|
+
├── sending-reliability.md # Idempotency, retry logic, errors
|
|
53
|
+
├── webhooks-events.md # Delivery events, webhook setup
|
|
54
|
+
└── list-management.md # Suppression lists, hygiene
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
Open `SKILL.md` - it has a routing table that directs you to the right resource based on what you need to do.
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Email Accessibility
|
|
2
|
+
|
|
3
|
+
Emails must be readable by screen readers, dark-mode clients, translation tools, and AI agents — not just sighted readers on a default inbox. The rules below are mechanical. Apply them every time.
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
### Set `lang` and `dir` on `<html>` and on `<body>`'s direct children (Serious)
|
|
8
|
+
|
|
9
|
+
Both attributes are needed in **two places**: on `<html>` *and* on the direct children of `<body>`. Several email clients strip the attributes from `<html>`, which is why duplicating them on the body's children is the single most common accessibility failure in production email.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<html lang="en" dir="ltr">
|
|
13
|
+
<head>
|
|
14
|
+
<title>Your weekly product updates</title>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<div lang="en" dir="ltr">
|
|
18
|
+
<!-- email content -->
|
|
19
|
+
</div>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- `lang`: a [BCP 47 language tag](https://developer.mozilla.org/en-US/docs/Glossary/BCP_47_language_tag) (`en`, `pt-BR`, `ja`, `ar`)
|
|
25
|
+
- `dir`: `ltr`, `rtl`, or `auto`
|
|
26
|
+
|
|
27
|
+
**Fallbacks when the correct values aren't available** (use only when you genuinely don't know):
|
|
28
|
+
|
|
29
|
+
- `dir="auto"` — lets the user agent infer direction from content
|
|
30
|
+
- `lang="und"` — marks the language as undetermined
|
|
31
|
+
|
|
32
|
+
Both fallbacks are worse than the correct value but much better than nothing. For multi-locale templates, pass the locale through; do not hardcode `en`.
|
|
33
|
+
|
|
34
|
+
### Mark layout tables as presentational (Serious)
|
|
35
|
+
|
|
36
|
+
Any `<table>` used for layout must have `role="presentation"` (or the equivalent `role="none"`). Otherwise screen readers announce "table, row 1 of N" for every layout row and the email becomes unusable. Prefer avoiding layout tables entirely; when you can't, mark them.
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
|
40
|
+
<tr>
|
|
41
|
+
<td>...</td>
|
|
42
|
+
</tr>
|
|
43
|
+
</table>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Leave a `<table>` without `role="presentation"` only when the data is genuinely tabular (line items, comparison rows). Tabular data should also use `<th scope="col">` for column headers.
|
|
47
|
+
|
|
48
|
+
### Use a single `<h1>` and nest headings in order (Mild)
|
|
49
|
+
|
|
50
|
+
Most emails should have one `<h1>` that names the email, with subheadings nested in order:`<h1>` → `<h2>` → `<h3>`. Never skip levels. Never fake a heading with bold `<p>`.
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<h1>Order confirmation</h1>
|
|
54
|
+
<h2>Items</h2>
|
|
55
|
+
<h2>Shipping</h2>
|
|
56
|
+
<h3>Address</h3>
|
|
57
|
+
<h3>Tracking</h3>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Headings are how assistive tech and AI clients navigate and summarize the email.
|
|
61
|
+
|
|
62
|
+
**Exception:** very short messages (SMS-style notifications, single-sentence alerts) may not need a heading at all. If the email body is one or two sentences, skip the `<h1>` rather than wrap a heading around the only content.
|
|
63
|
+
|
|
64
|
+
### Every link must have discernible text (Serious)
|
|
65
|
+
|
|
66
|
+
Every `<a>` must contain text content that a screen reader can announce. The most common failure is a linked image with no alt text.
|
|
67
|
+
|
|
68
|
+
A **linked image is never decorative.** It's functional, so its `alt` must describe what clicking does, not just what the image looks like.
|
|
69
|
+
|
|
70
|
+
```html
|
|
71
|
+
<!-- Wrong: linked image with no accessible name -->
|
|
72
|
+
<a href="/order/123">
|
|
73
|
+
<img src="view-order.png" alt="">
|
|
74
|
+
</a>
|
|
75
|
+
|
|
76
|
+
<!-- Right: alt describes the action -->
|
|
77
|
+
<a href="/order/123">
|
|
78
|
+
<img src="view-order.png" alt="View order #123">
|
|
79
|
+
</a>
|
|
80
|
+
|
|
81
|
+
<!-- Also right: visible text alongside the image -->
|
|
82
|
+
<a href="/order/123">
|
|
83
|
+
<img src="icon.png" alt="">
|
|
84
|
+
View order #123
|
|
85
|
+
</a>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
When the visible link text can't carry enough information, add visually hidden text inside the `<a>` (see [goodemailcode.com/email-accessibility/visually-hidden-text](https://www.goodemailcode.com/email-accessibility/visually-hidden-text)). `aria-label` and `title` on `<a>` have limited support in email clients. Prefer real text content or visually hidden text.
|
|
89
|
+
|
|
90
|
+
### Link text must describe the destination (Moderate)
|
|
91
|
+
|
|
92
|
+
Even when a link has text, it must describe where the link goes. Never use "click here," "learn more," "read more," or bare URLs. Screen reader users often navigate by jumping between link texts with no surrounding context.
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<!-- Wrong -->
|
|
96
|
+
<a href="...">click here</a>
|
|
97
|
+
<a href="...">https://resend.com/blog/...</a>
|
|
98
|
+
|
|
99
|
+
<!-- Right -->
|
|
100
|
+
<a href="...">Read the 2026 accessibility report</a>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Write meaningful alt text — and use `alt=""` for decorative images (Critical)
|
|
104
|
+
|
|
105
|
+
Two distinct rules, both mandatory. `alt` must always be present; the value depends on the image's role.
|
|
106
|
+
|
|
107
|
+
**Meaningful images** (product shots, charts, screenshots, anything carrying information): describe purpose and key details in context.
|
|
108
|
+
|
|
109
|
+
```html
|
|
110
|
+
<!-- Wrong: redundant, vague -->
|
|
111
|
+
<img src="..." alt="image">
|
|
112
|
+
<img src="..." alt="photo of a bike">
|
|
113
|
+
|
|
114
|
+
<!-- Right: purpose + key details -->
|
|
115
|
+
<img src="..." alt="Red bicycle leaning against a brick wall on a rainy street">
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Decorative images** (spacers, dividers, background flourishes, pure branding ornaments): use an empty `alt=""`. This tells screen readers to skip them. Never omit the attribute entirely — omitting it and `alt=""` are not equivalent; some screen readers announce the filename when `alt` is absent.
|
|
119
|
+
|
|
120
|
+
```html
|
|
121
|
+
<img src="divider.png" alt="" role="presentation">
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If an image conveys no information that isn't already in the surrounding text, it is decorative. If it's inside an `<a>`, it is **not** decorative — see the "Every link must have discernible text" rule.
|
|
125
|
+
|
|
126
|
+
### Include a `<title>` tag (Serious)
|
|
127
|
+
|
|
128
|
+
Many clients and assistive technologies read `<title>` before anything else. It's also shown when the email is viewed as a web page. Treat it like the subject line, not the brand name.
|
|
129
|
+
|
|
130
|
+
```html
|
|
131
|
+
<head>
|
|
132
|
+
<title>Your weekly product updates from Resend</title>
|
|
133
|
+
</head>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
If the per-email title is hard to populate, a generic but specific fallback like `"Email from {Brand Name}"` still beats nothing.
|
|
137
|
+
|
|
138
|
+
### Hit 4.5:1 color contrast, then check dark mode (Serious)
|
|
139
|
+
|
|
140
|
+
- Body text and links: **4.5:1** minimum against the background (WCAG AA)
|
|
141
|
+
- Large text (≥18pt, or ≥14pt bold): **3:1** minimum
|
|
142
|
+
- Never rely on color alone to convey meaning (error states, status badges) — pair it with text or an icon
|
|
143
|
+
|
|
144
|
+
Verify with the [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/) or browser devtools.
|
|
145
|
+
|
|
146
|
+
**Dark mode.** Outlook, Apple Mail, and others force dark mode and derive dark colors from your light ones. Healthy starting contrast keeps the auto-inverted version readable. Always preview in dark mode before shipping.
|
|
147
|
+
|
|
148
|
+
## Priority order
|
|
149
|
+
|
|
150
|
+
When you can't fix everything, fix in this order:
|
|
151
|
+
|
|
152
|
+
1. **Critical** — missing or misused `alt` on images
|
|
153
|
+
2. **Serious** — `lang`/`dir` (on `<html>` and body children), `role="presentation"` on layout tables, links without discernible text, missing `<title>`, color contrast
|
|
154
|
+
3. **Moderate** — non-descriptive link text ("click here")
|
|
155
|
+
4. **Mild** — missing `<h1>` (skip the fix for very short messages)
|
|
156
|
+
|
|
157
|
+
## Authoring checklist
|
|
158
|
+
|
|
159
|
+
Run this on every template:
|
|
160
|
+
|
|
161
|
+
- [ ] `<html>` has `lang` and `dir`; direct children of `<body>` also have `lang` and `dir`
|
|
162
|
+
- [ ] `<title>` set on `<head>`, specific to this email (not the brand name)
|
|
163
|
+
- [ ] Layout `<table>` elements have `role="presentation"` (or `role="none"`)
|
|
164
|
+
- [ ] At most one `<h1>` (or none, for very short messages); `<h2>`/`<h3>` nested in order
|
|
165
|
+
- [ ] Every `<a>` has discernible text — visible text, descriptive alt on linked images, or visually hidden text
|
|
166
|
+
- [ ] Every link describes its destination — no "click here," "learn more," or bare URLs
|
|
167
|
+
- [ ] Every meaningful image has descriptive `alt`; every decorative image has an explicit `alt=""`
|
|
168
|
+
- [ ] No linked image with `alt=""` (linked images are functional, never decorative)
|
|
169
|
+
- [ ] Body text passes 4.5:1 contrast and stays readable in dark mode
|
|
170
|
+
- [ ] Plain-text alternative is sent alongside the HTML version
|
|
171
|
+
|
|
172
|
+
## Testing
|
|
173
|
+
|
|
174
|
+
- **Automated.** Run the email through [Parcel's accessibility checker](https://parcel.io/docs/dev-tools/accessibility-checker) (the same tool the EMC report uses; available on the free plan). It catches most of the rules above.
|
|
175
|
+
- **Screen reader pass.** macOS VoiceOver (`Cmd+F5`) or NVDA on Windows. Listen top to bottom; if anything is confusing, fix the markup.
|
|
176
|
+
- **Contrast.** [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/).
|
|
177
|
+
- **Dark mode.** Send a test to Outlook (Windows/web), Apple Mail with dark appearance, Gmail iOS and Android.
|
|
178
|
+
|
|
179
|
+
Automated tests do not catch everything. They will not tell you whether alt text actually matches the image, whether headings make semantic sense, or whether text inside an image is readable on narrow viewports. Even the three brands that passed every automated check in the EMC 2026 report had judgment-level issues like generic alt text on decorative images, alt text that didn't match the image, and 10px footer text. Treat automation as a floor, not a ceiling.
|
|
180
|
+
|
|
181
|
+
## Related
|
|
182
|
+
|
|
183
|
+
- [Transactional Emails](./transactional-emails.md) — content patterns for password resets, OTPs, receipts
|
|
184
|
+
- [Marketing Emails](./marketing-emails.md) — newsletter and campaign best practices
|
|
185
|
+
- [Compliance](./compliance.md) — legal requirements that overlap with accessibility (e.g., clear unsubscribe text)
|
|
186
|
+
|
|
187
|
+
## Tooling
|
|
188
|
+
|
|
189
|
+
When generating templates with React Email, the latest version handles several of the structural rules: `<Html>` sets `lang`/`dir`, `<Img>` defaults to `alt=""`, `<Markdown>` tables render `role="presentation"`, and `<Preview>` emits a `<title>`. Upgrade with `npm install react-email@latest`. The content rules — heading hierarchy, descriptive alt and link text, contrast, the linked-image rule — still have to be applied by hand.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Email Compliance
|
|
2
|
+
|
|
3
|
+
Legal requirements for email by jurisdiction. **Not legal advice—consult an attorney for your specific situation.**
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
| Law | Region | Key Requirement | Penalty |
|
|
8
|
+
|-----|--------|-----------------|---------|
|
|
9
|
+
| CAN-SPAM | US | Opt-out mechanism, physical address | $53k/email |
|
|
10
|
+
| GDPR | EU | Explicit opt-in consent | €20M or 4% revenue |
|
|
11
|
+
| CASL | Canada | Express consent, opt-out mechanism | $1M (individual) to $10M (organization) CAD |
|
|
12
|
+
|
|
13
|
+
## CAN-SPAM (United States)
|
|
14
|
+
|
|
15
|
+
**Requirements:**
|
|
16
|
+
- Accurate header info (From, To, Reply-To)
|
|
17
|
+
- Non-deceptive subject lines
|
|
18
|
+
- Physical mailing address in every email
|
|
19
|
+
- Clear opt-out mechanism
|
|
20
|
+
- Honor opt-out within 10 business days
|
|
21
|
+
|
|
22
|
+
**Transactional emails:** Can send without opt-in if related to a transaction and not promotional.
|
|
23
|
+
|
|
24
|
+
## GDPR (European Union)
|
|
25
|
+
|
|
26
|
+
**Requirements:**
|
|
27
|
+
- Explicit opt-in consent (not pre-checked boxes)
|
|
28
|
+
- Consent must be freely given, specific, informed
|
|
29
|
+
- Easy to withdraw consent (as easy as giving it)
|
|
30
|
+
- Right to access data and deletion ("right to be forgotten")
|
|
31
|
+
- Process unsubscribe immediately
|
|
32
|
+
|
|
33
|
+
**Consent records:** Document who, when, how, and what they consented to.
|
|
34
|
+
|
|
35
|
+
**Transactional emails:** Can send based on contract fulfillment or legitimate interest.
|
|
36
|
+
|
|
37
|
+
## CASL (Canada)
|
|
38
|
+
|
|
39
|
+
**Consent types:**
|
|
40
|
+
- **Express consent:** Explicit opt-in (ideal)
|
|
41
|
+
- **Implied consent:** Existing business relationship (2 years) or inquiry (6 months)
|
|
42
|
+
|
|
43
|
+
**Requirements:**
|
|
44
|
+
- Clear sender identification that will be valid for 60 days after send
|
|
45
|
+
- Unsubscribe functional for 60 days after send
|
|
46
|
+
- Process unsubscribe no later than 10 business days
|
|
47
|
+
- Keep consent records 3 years after expiration
|
|
48
|
+
|
|
49
|
+
## Other Regions
|
|
50
|
+
|
|
51
|
+
| Region | Law | Key Points |
|
|
52
|
+
|--------|-----|------------|
|
|
53
|
+
| Australia | Spam Act 2003 | Consent required, honor unsubscribe within 5 days |
|
|
54
|
+
| UK | PECR + GDPR | Same as GDPR |
|
|
55
|
+
| Brazil | LGPD | Similar to GDPR, explicit consent for marketing |
|
|
56
|
+
|
|
57
|
+
## Unsubscribe Requirements Summary
|
|
58
|
+
|
|
59
|
+
| Law | Timing | Notes |
|
|
60
|
+
|-----|--------|-------|
|
|
61
|
+
| CAN-SPAM | 10 business days | Must work 30 days after send |
|
|
62
|
+
| GDPR | Immediately | Must be as easy as opting in |
|
|
63
|
+
| CASL | 10 business days | Must work 60 days after send |
|
|
64
|
+
|
|
65
|
+
**Universal best practices:** Prominent link, one-click when possible, no login required, free, confirm action.
|
|
66
|
+
|
|
67
|
+
### List-Unsubscribe Header (Required for Bulk Senders)
|
|
68
|
+
|
|
69
|
+
Gmail, Yahoo, and Microsoft require `List-Unsubscribe` headers. Without them, bulk emails may be rejected or spam-filtered.
|
|
70
|
+
|
|
71
|
+
**Required headers:**
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
headers: {
|
|
75
|
+
'List-Unsubscribe': '<https://example.com/unsubscribe>',
|
|
76
|
+
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Your unsubscribe endpoint must:**
|
|
81
|
+
- Accept POST requests — return `200` or `202` with a blank page
|
|
82
|
+
- Display standard unsubscribe page for GET requests
|
|
83
|
+
- Stop sending within 48 hours of the request
|
|
84
|
+
|
|
85
|
+
## Managing preferences vs Unsubscribe from all
|
|
86
|
+
|
|
87
|
+
Most legistlations require a one-click unsubscribe. `Managing preferences` is a nice-to-have and can lead to lower unsubscribe rate but doesn't replace `Unsubscribe`. If possible, offer both.
|
|
88
|
+
|
|
89
|
+
## Consent Management
|
|
90
|
+
|
|
91
|
+
**Record:**
|
|
92
|
+
- Email address
|
|
93
|
+
- Date/time of consent
|
|
94
|
+
- Method (form, checkbox)
|
|
95
|
+
- What they consented to
|
|
96
|
+
- Source (which page/form)
|
|
97
|
+
|
|
98
|
+
**Storage:** Database with timestamps, audit trail of changes, link to user account.
|
|
99
|
+
|
|
100
|
+
## Data Retention
|
|
101
|
+
|
|
102
|
+
| Law | Requirement |
|
|
103
|
+
|-----|-------------|
|
|
104
|
+
| GDPR | Keep only as long as necessary, delete when no longer needed |
|
|
105
|
+
| CASL | Keep consent records 3 years after expiration |
|
|
106
|
+
|
|
107
|
+
**Best practice:** Have clear retention policy, honor deletion requests promptly, review and clean regularly.
|
|
108
|
+
|
|
109
|
+
## Privacy Policy Must Include
|
|
110
|
+
|
|
111
|
+
- What data you collect
|
|
112
|
+
- How you use data
|
|
113
|
+
- Who you share data with
|
|
114
|
+
- User rights (access, deletion)
|
|
115
|
+
- How to contact about privacy
|
|
116
|
+
|
|
117
|
+
## International Sending
|
|
118
|
+
|
|
119
|
+
**Best practice:** Follow the most restrictive requirements (usually GDPR) to ensure compliance across all regions.
|
|
120
|
+
|
|
121
|
+
## Related
|
|
122
|
+
|
|
123
|
+
- [Email Capture](./email-capture.md) - Implement consent forms and double opt-in
|
|
124
|
+
- [Marketing Emails](./marketing-emails.md) - Consent and unsubscribe requirements
|
|
125
|
+
- [List Management](./list-management.md) - Handle unsubscribes and deletion requests
|