Email Allow-List

Last updated on October 10, 2026.

Only let listed email addresses sign in, without revealing who is on the list.

An email allow-list means only the addresses you list can sign in. On Insites you build it into the page that sends the sign-in code. It takes one lookup and a few lines of Liquid.

What You Need Before You Start

Choose Where the List Lives

There are two ways to keep the list. Pick one.

  • The users that exist. An address is allowed when a user with that email exists and has a member role. This is the simplest choice when every allowed person needs an account anyway. You add a person by creating their user and giving them the role.
  • A table of allowed addresses. An address is allowed when it appears in an allowed_email table. You check the table before you send anything. Use this when the list changes often, or when people should not have an account until they first sign in.

Note: Do not treat every user as allowed. On an instance, every CRM contact is a user record, and every Instance Admin administrator is also a CRM contact. That is why the first option checks for a role, not just for a user.

If you use the first option, do not also build a public sign-up page. Anyone who can create their own user could put themselves on the list.

Always Give the Same Answer

The page must answer in exactly the same way whether or not the address is allowed. Use the same status code and the same message. Otherwise anyone can use the page to test which addresses are on your list.

A message such as If that email is allowed, we sent a sign-in code. works for both cases.

Worked Example: A Table of Allowed Addresses

This example uses the second option. It answers at /api/auth/request-code.

Step 1: Create the Table

Add a schema file. The name must match the file name.

app/schema/allowed_email.yml

name: allowed_email
properties:
  - name: email
    type: string

Store every address in lower case, so the lookup in Step 3 always matches.

Step 2: Add a Lookup Query

The table name and the field name are passed in as variables, not written into the query.

app/graphql/records/find_by.graphql

query find_by($table: String!, $field: String!, $value: String!, $limit: Int!) {
  records(
    per_page: $limit
    filter: { table: { value: $table }, properties: [{ name: $field, value: $value }] }
    sort: [{ created_at: { order: DESC } }]
  ) {
    total_entries
    results { id created_at properties }
  }
}

Step 3: Build the Endpoint

A JSON endpoint is a page with format: json in its front matter. The slug does not end in .json, so the page answers at a plain path.

app/views/pages/api/auth/request-code.liquid

---
slug: api/auth/request-code
method: post
format: json
---
{% liquid
  assign email = context.params.email | downcase | strip
  if email != blank
    graphql found = 'records/find_by', table: 'allowed_email', field: 'email', value: email, limit: 1
    if found.records.total_entries > 0
      function sent = 'auth/send_code', email: email
    endif
  endif

  assign body = {}
  assign body.message = 'If that email is allowed, we sent a sign-in code.'
  assign out = body | json
  echo out
%}

auth/send_code stands for your partial that creates the code and emails it. See Passwordless Sign-In.

The request body can be JSON or a form. Both arrive in context.params.

Step 4: Test Both Cases

  1. Send a request with an address that is on the list. Check the email arrives.
  2. Send a request with an address that is not on the list. Check no email is sent.
  3. Compare the two responses. The status code and the body must be the same.

The Same Check With Users

For the first option, look up a user instead of a table row. The role is stored on the user as an array property named roles. Admin Role Instead of a Shared Password shows how to give a user a role.

Step 1: Declare the Roles Property

Do this before any role check. Add roles to your users as an array property. If your instance already has app/user.yml, add the property to it.

app/user.yml

properties:
  - name: roles
    type: array

Note: Without this file the check matches nobody and fails without a sound. The query answers with the GraphQL error array_contains only works for arrays, but a Liquid page does not raise it. The lookup finds no one, so every address is treated as not allowed and nobody can sign in. No Liquid error tells you why.

Step 2: Add the Lookup Query

app/graphql/users/find_with_role.graphql

query find_with_role($email: String!, $field: String!, $role: String!) {
  users(
    per_page: 1
    filter: { email: { value: $email }, properties: [{ name: $field, array_contains: [$role] }] }
  ) {
    total_entries
  }
}

Step 3: Call It From the Endpoint

Call it from the endpoint in place of the table lookup:

graphql found = 'users/find_with_role', email: email, field: 'roles', role: 'member'
if found.users.total_entries > 0

Next Steps

Have a suggestion for this page?

Didn't quite find what you are looking for or have feedback on how we can make the content better then we would love to hear from you. Please provide us feedback and we will get back to you shortly.