extension-email-verification
Support for sending an email with a link the recipient can click to prove they own the email address.
By caffeinelabs · 19,507 installs
npx skills add caffeinelabs/skills --skill extension-email-verification
Source repository · Upstream listing
Email — Verification
Email verification extension for [Caffeine AI](https://caffeine.ai?utm source=caffeine skill&utm medium=referral).
Overview
This skill adds email address verification via a click to verify link that lands on the app's own frontend. The backend mints and stores the link token ( verificationTokens ) and confirms it through MixinEmailVerification ; verifiedEmails tracks verified addresses. The frontend renders the /verify email page, where the recipient clicks a button to confirm.
Required Setup Checklist
All five steps are mandatory.
1. mops dependencies — mops add caffeineai email verification and mops add caffeineai email .
2. Two stable state fields — verifiedEmails : VerifiedEmails.State and verificationTokens : VerificationTokens.State , initialised in the migration chain head with VerifiedEmails.new() and VerificationTokens.new() .
3. Mixin invocation — include MixinEmailVerification(verifiedEmails, verificationTokens) in main.mo .
4. Sending — EmailVerification.sendVerificationEmail(verificationTokens, fromUsername, recipients, subject, htmlBody) ; the body MUST contain {{VERIFICATION URL}} .
5. Frontend npm package and route — @caffeineai/email verification installed, and a public route /verify email (reachable without signing in) that renders a button calling confirm() .
CRITICAL: the link in the email is https://<app domain /verify email?token=… . Without the frontend route the recipient lands on a missing page and the address is never verified.
Backend
This component is for sending an email to users with a verification link which the user can click to prove they own the email address.
To check if an email address has been verified
Use the prefabricated module mo:caffeineai email verification/verifiedEmails.mo which cannot be modified.
To check whether an email is verified use the contains function. Do NOT try to track the email verification status independently by storing it against the user profile.
Pending verification links
Use the prefabricated module mo:caffeineai email verification/verificationTokens.mo which cannot be modified. It holds the tokens of links sent but not yet clicked; a token is single use and expires after 24 hours. The app only declares the state and passes it around.
To handle the verification link
Use the prefabricated module mo:caffeineai email verification/verificationMixin.mo which cannot be modified.
MixinEmailVerification takes both states. It adds caffeineEmailConfirmVerification(token) , which the app's frontend calls from the verification page: it marks the address the token was sent to as verified and returns it. Anyone holding a valid token can call it, the anonymous principal included, so the recipient needs no account.
For sending users a verification email
Use the sendVerificationEmail function of the prefabricated module mo:caffeineai email verification/verification.mo which cannot be modified. Do NOT call EmailClient.broadcast from app code: this module prepares the tokens and the link template it sends.
It returns a SendResult which is ok if the email is sent successfully otherwise err(error) with the error text.
Each recipient receives an individual email with a specific verification link for them, pointing at the app's /verify email page.
The htmlBody MUST contain the placeholder text {{VERIFICATION URL}}; the function returns err when it is missing.
The mail goes through the mail gateway with the settings the platform injects on every deploy ( INTEGRATIONS GATEWAY URL / INTEGRATIONS GATEWAY API KEY ); without them the function returns err .
Example usage with endpoints for registering a user and for checking whether a user is verified.
The migration chain head:
Upgrading from the previous version
An app built with caffeineai email verification 0.3.0 re pins with mops add caffeineai email verification@0.3.1 and mops add caffeineai email@0.5.0 . Nothing in the app code changes. Both compile with ic 5.0.0, where 0.3.0 fails.
An app built with caffeineai email verification 0.2.x re pins with mops add caffeineai email verification@0.3.0 and mops add caffeineai email@0.4.0 , and bumps @caffeineai/email verification to 0.3.0. Nothing in the app code changes. The mixin no longer has the caffeineEmailVerify callback of the transport canister: verification mail now always goes through the mail gateway, and the links in mail the transport sent before 0.2.0 stop working.
An app built with caffeineai email verification 0.1.x and caffeineai email 0.2.x needs all of these, then the re pin above:
1. Re pin: mops add caffeineai email verification@0.2.0 and mops add caffeineai email@0.3.0 .
2. Declare the new stable field verificationTokens : VerificationTokens.State and add it to the migration chain (a new migration file initialising it with VerificationTokens.new() ; verifiedEmails keeps its shape and its data).
3. Change include MixinEmailVerification(verifiedEmails) to include MixinEmailVerification(verifiedEmails, verificationTokens) .
4. Replace EmailClient.sendVerificationEmail(fromUsername, recipients, subject, htmlBody) with EmailVerification.sendVerificationEmail(verificationTokens, fromUsername, recipients, subject, htmlBody) (import mo:caffeineai email verification/verification ).
5. Add the frontend package and the /verify email route described below.
Frontend
Install @caffeineai/email verification . It exports the prefabricated hook useEmailVerification , which cannot be modified:
Rules for the verification page:
Add a route at /verify email that is reachable WITHOUT signing in: recipients open it from their mail client and have no session. The backend method takes the token as its only credential, so the anonymous actor from useActor is enough.
Read the token with the hook (it takes ?token= from the URL itself) and render a button that calls confirm() . NEVER call confirm() on page load or in an effect: mail clients, link previewers and scanners fetch the URL without a person behind it, and a fetch must verify nothing.
Show the outcome from status : done with the verified email , invalid for a missing, used or expired link (offer to request a new email), error with the message.
Example page:
Register it with the app's TanStack Router alongside the other routes, as a child of the root route with path: '/verify email' , outside any authenticated layout.
If there is a UI for the admin to enter the content of a verification email then indicate that the placeholder text {{VERIFICATION URL}} must be present in the email body.