# Embeddable email links with sign-in tokens (Legacy)

> This guide is for users who want to build a custom flow. To use a _prebuilt_ UI, use the [Account Portal pages](https://clerk.com/docs/guides/account-portal/overview.md?sdk=nextjs) or [prebuilt components](https://clerk.com/docs/nextjs/reference/components/overview.md).

> This guide uses the Core 2 `useSignIn()` and `useSignUp()` hooks, which are available in Core 3 SDKs by adding the `/legacy` subpath to the import path. If you're using a Core 2 SDK, remove the `/legacy` subpath.

An "email link" is a link that, when visited, will automatically authenticate your user so that they can perform some action on your site with less friction than if they had to sign in manually. You can create email links with Clerk by generating a sign-in token.

Common use cases include:

- Welcome emails when users are added off a waitlist
- Promotional emails for users
- Recovering abandoned carts
- Surveys or questionnaires

This guide will demonstrate how to generate a sign-in token and use it to sign in a user.

1. ## Generate a sign-in token

   [Sign-in tokens](https://clerk.com/docs/reference/backend-api/tag/sign-in-tokens/POST/sign_in_tokens){{ target: '_blank' }} are JWTs that can be used to sign in to an application without specifying any credentials. A sign-in token can be used **once**, and can be consumed from the Frontend API using the [ticket](https://clerk.com/docs/nextjs/reference/objects/sign-in.md#sign-in-create-params) strategy, which is demonstrated in the following example.

   > By default, sign-in tokens expire in 30 days. You can optionally specify a different duration in seconds using the `expires_in_seconds` property.

   The following example demonstrates a cURL request that creates a valid sign-in token:

   ```bash
   curl 'https://api.clerk.com/v1/sign_in_tokens' \
     -X POST \
     -H 'Authorization: Bearer {{secret}}' \
     -H 'Content-Type: application/json' \
     -d '{ "user_id": "user_123" }'
   ```

   This will return a `url` property, which can be used as your email link. Keep in mind that this link will use the [Account Portal sign-in page](https://clerk.com/docs/guides/account-portal/overview.md?sdk=nextjs#sign-in) to sign in the user.

   If you would rather use your own sign-in page, you can use the `token` property that is returned. Add the `token` as a query param in any link, such as the following example:

   `https://your-site.com/accept-token?token=<INSERT_TOKEN_HERE>`

   Then, you can embed this link anywhere, such as an email.
2. ## Build a custom flow for signing in with a sign-in token

   To handle email links with sign-in tokens, you must set up a page in your frontend that detects the token, signs the user in, and performs any additional actions you need.

   The following example demonstrates basic code that detects a token in the URL query params and uses it to initiate a sign-in with Clerk.

   filename: app/accept-token/page.tsx

   ```tsx
   'use client'
   import { useUser } from '@clerk/nextjs'
   import { useSignIn } from '@clerk/nextjs/legacy'
   import { useEffect, useState } from 'react'
   import { useSearchParams } from 'next/navigation'

   export default function Page() {
     const [loading, setLoading] = useState<boolean>(false)
     const { signIn, setActive } = useSignIn()
     const { isSignedIn, user } = useUser()

     // Get the token from the query params
     const signInToken = useSearchParams().get('token')

     useEffect(() => {
       if (!signIn || !setActive || !signInToken || user || loading) {
         return
       }

       const createSignIn = async () => {
         setLoading(true)
         try {
           // Create the `SignIn` with the token
           const signInAttempt = await signIn.create({
             strategy: 'ticket',
             ticket: signInToken as string,
           })

           // If the sign-in was successful, set the session to active
           if (signInAttempt.status === 'complete') {
             setActive({
               session: signInAttempt.createdSessionId,
             })
           } else {
             // If the sign-in attempt is not complete, check why.
             // User may need to complete further steps.
             console.error(JSON.stringify(signInAttempt, null, 2))
           }
         } catch (err) {
           // See https://clerk.com/docs/guides/development/custom-flows/error-handling
           // for more info on error handling
           console.error('Error:', JSON.stringify(err, null, 2))
         } finally {
           setLoading(false)
         }
       }

       createSignIn()
     }, [signIn, setActive, signInToken, user, loading])

     if (!signInToken) {
       return <div>No token provided.</div>
     }

     if (!isSignedIn) {
       // Handle signed out state
       return null
     }

     if (loading) {
       return <div>Signing you in...</div>
     }

     return <div>Signed in as {user.id}</div>
   }
   ```

---

## Sitemap

[Overview of all docs pages](https://clerk.com/docs/llms.txt)
