Skip to content

Refuse Firebase signups whose address cannot receive mail

Integration Signups and authentication

Add a blocking function to Firebase Authentication that checks each new address with PricklyMails and refuses the account when it cannot receive mail.

No connector needed: at each signup, Firebase calls the PricklyMails API from a beforeUserCreated blocking function, and an address that cannot receive mail is refused before the account exists.

Verify for free

Step by step

Check each signup in four steps

Before you start

Blocking functions need your project upgraded to Firebase Authentication with Identity Platform, from Authentication then Settings in the Firebase console. Deploying a function needs the Blaze plan.

  1. In PricklyMails

    Create an API key in PricklyMails

    1. In the dashboard, open API Keys and click New key.
    2. Under Environment, pick Test first: the key starts with pm_test_, answers from canned results, bills nothing and contacts no mail server.
    3. Copy the key as soon as it shows: it is shown only once.

    Tip

    The Free plan holds one key: once everything works, revoke the test key, then create the one under Live.

  2. In Firebase

    Store the key and add the function in Firebase

    The code is a Cloud Functions for Firebase function (2nd gen, Node.js) that Firebase Authentication runs before it saves a new user. The key stays in Cloud Secret Manager, where only the functions that bind it can read it.

    1. In your project directory, run firebase init functions and choose JavaScript, unless the project already has a functions folder.
    2. Run firebase functions:secrets:set PRICKLYMAILS_KEY, then enter your test key when the CLI asks for its value.
    3. Add the code below to functions/index.js.

    The code asks PricklyMails to answer within a few seconds (Prefer: wait) and stops waiting soon after, so a signup never hangs on the answer. It refuses the signup only when the recommendation is remove, and lets it through otherwise: a risky address, no answer in time, an error.

    functions/index.js
    const { defineSecret } = require('firebase-functions/params')
    const {
        beforeUserCreated,
        HttpsError,
    } = require('firebase-functions/v2/identity')
    
    const VERIFY_URL = 'https://api.pricklymails.com/api/verify'
    const REFUSAL =
        'This email address cannot receive mail. ' +
        'Check it for a typo, or use another one.'
    
    const pricklymailsKey = defineSecret('PRICKLYMAILS_KEY')
    
    // Firebase gives the function 7 seconds: wait 3 for the answer, give up at 4.
    async function recommendationFor(email, key) {
        try {
            const response = await fetch(VERIFY_URL, {
                method: 'POST',
                headers: {
                    Authorization: `Bearer ${key}`,
                    'Content-Type': 'application/json',
                    Prefer: 'wait=3',
                },
                body: JSON.stringify({ email }),
                signal: AbortSignal.timeout(4000),
            })
            if (200 !== response.status) {
                return null
            }
            const { verification } = await response.json()
            return verification?.recommendation ?? null
        } catch {
            return null
        }
    }
    
    exports.beforecreated = beforeUserCreated(
        { secrets: [pricklymailsKey] },
        async (event) => {
            const email = event.data?.email
            const key = pricklymailsKey.value()
            if (email && 'remove' === (await recommendationFor(email, key))) {
                throw new HttpsError('invalid-argument', REFUSAL)
            }
        },
    )
    
    How the API answers in one call

    Tip

    To limit cold starts, keep an instance warm with minInstances: 1 next to secrets in the function options. An instance kept running is billed at idle rates.

  3. In Firebase

    Deploy the function to Firebase

    Deploying the function is what turns it on: from then on, Firebase Authentication creates an account only when the function completes without an error.

    1. From your project directory, run firebase deploy --only functions.
    2. After each change to the code, or to the value of PRICKLYMAILS_KEY, run the same command again.

    Warning

    Deleting the function is not enough to turn it off: also unregister its trigger from Firebase Authentication, or no one can sign up or sign in to your app anymore.

  4. In Firebase

    Try it with the test key

    1. Sign up with an address that starts with invalid, such as invalid@example.com: the test key answers remove, and the signup is refused.
    2. Sign up with another address, such as valid@example.com: the account is created.
    3. Where you stored the key, replace it with a key created under Live, which starts with pm_live_: from then on, each signup is verified for real and billed like any verification.

What each signup gets

The code reads the recommendation the API gives the address, the same one a list's report gives.

Remove

Refused: the address does not exist, its domain cannot receive mail, or it is disposable.

Firebase Authentication does not create the account. In your app, the client SDK rejects the signup call with an internal error, auth/internal-error, whose message holds the refusal text among technical details. Catch it in your signup form and, when the message mentions Cloud Function, show the person the refusal text or your own words rather than the raw message.

Review

Let through: a domain that accepts any address, a generic address such as contact@, or a server that did not answer. The address may work.

Send

Let through: the address is valid, or hosted by a large provider that accepts every address.

No answer in time
Firebase gives the function 7 seconds: past that, Firebase Authentication returns an error and the signup fails, whatever the address. The code stops waiting for PricklyMails after 4 seconds and lets the signup through, which leaves the function time to start and answer.

Frequently asked questions

Which signups go through the function?

Every new user Firebase Authentication creates, whatever the sign-in method, except with anonymous and custom authentication, which never trigger blocking functions. A user created without an email address, with a phone number for instance, goes through unchecked: the code returns before it calls PricklyMails.

SourceFirebase's official help center

Does the function run at every sign-in?

No. Firebase runs beforeUserCreated only before it saves a new user. An existing user signing in triggers beforeUserSignedIn, which this code does not use: a sign-in never calls PricklyMails and costs no verification.

SourceFirebase's official help center

What does the setup cost on the Firebase side?

On the Blaze plan, Firebase Authentication with Identity Platform bills monthly active users beyond a no-cost tier of 50,000 for email and social sign-in. Cloud Functions has a monthly no-cost quota, then pay-as-you-go pricing, and Secret Manager holds 6 active secret versions at no cost.

SourceFirebase's official help center

Is there a PricklyMails extension for Firebase?

No, and there is nothing to install: the code above calls the PricklyMails API with your key.

These trademarks belong to their respective owners. Naming them implies no partnership.

Check your list before your next send.

1,000 tokens free when you sign up, no credit card. After that, you only pay for answers.