Replacing a Wix Form Email Notification Workflow with a Reliable Weekly Digest

Photo by Thierry Chabot on Unsplash

The Problem

I was working with a Wix website that used a contact form for website enquiries. The form itself appeared to be working normally: visitors could submit the form, submissions appeared in the Wix dashboard, and contacts were being created as expected.

The problem was what happened afterwards.

The original setup relied on a Wix Forms email notification automation that was supposed to send an email whenever someone submitted the contact form. In practice, some submissions were not resulting in an email being delivered to the intended recipient.

This was particularly confusing because there was no obvious failure at the form level. The submissions existed in Wix, the contacts existed in the Contacts section, and the automation itself appeared to be active.

The issue became more apparent when looking at the automation’s run log. Some submissions showed a status of Skipped rather than Ended.

Opening one of the skipped runs showed:

This run was skipped since the data didn’t match the criteria set in the trigger.

The underlying reason reported by Wix was:

TRIGGER_FILTERS_NOT_PASSED


That meant the automation had received the form-submission event, but the data associated with that event did not satisfy the trigger’s configured conditions.

The important distinction was that the enquiry had not failed to reach Wix. Wix had received it. The automation simply decided not to continue processing it.

That made the problem considerably more frustrating because the form appeared healthy from the front end.


Investigating the Wix Form

The first step was to inspect the form itself.

The Wix Form settings exposed several sections, including:

  • Main
  • Settings
  • Submit Message
  • Payment
  • Contacts
  • Automations
  • Email Marketing
  • Support

The Contacts section confirmed that form submissions were being saved to the Wix Contact List.

The Automations section, however, did not reveal another hidden automation that could explain the behaviour.

The original automation was configured around a specific form, shown as:

Specific → Contact Form


At first, this raised another possibility: perhaps the automation was associated with an older version of the form.

That possibility was worth investigating because Wix has evolved its Forms infrastructure over time, and older Wix Forms can appear differently from newer form components.

When the trigger was opened, however, Wix displayed the form as view only:

This step was set as view-only by the app, so it can't be edited.


The trigger showed:

Old Wix Forms
Form submitted


with the specific contact form selected.

This was an important clue.

The automation was not simply a normal email workflow that could be edited and reconfigured from the automation editor. The form trigger itself was controlled by the Wix Forms app.

That meant there was no useful way to go into the existing automation and simply redefine the form trigger or repair whatever relationship existed between the form and the app-controlled trigger.


Looking at the Automation Run Log

The run log provided the most useful evidence.

Successful runs showed the expected sequence:

Form submitted
        ↓
Send an email
        ↓
Show in Inbox
        ↓
Ended


But skipped submissions stopped at the trigger.

For example, a skipped run showed:

Form submitted
        ↓
Send an email
        ↓
Show in Inbox


with the actions disabled because the trigger had not passed its conditions.

The run information explicitly stated that the data did not match the trigger criteria.

This explained why searching for the recipient’s email address in the Contacts section could show evidence of automation emails while individual form submissions could still be skipped.

The contact existed. The form submission existed. The automation existed.

But the automation was not guaranteed to process every submission.

At this point, rather than continuing to try to repair an old, app-controlled Wix Forms email notification trigger, I decided to remove the dependency on that notification workflow entirely. The form itself would remain in place and continue collecting submissions.


The Alternative: Build the Email Digest with Velo

The requirement was actually simpler than the original automation suggested.

Instead of:

Visitor submits form
        ↓
Automation immediately sends email


the desired workflow was:

Visitor submits form
        ↓
Wix stores submission
        ↓
Once a week
        ↓
Backend code retrieves recent submissions
        ↓
Build one digest
        ↓
Send one email


This has an important advantage.

The form itself is only responsible for doing what it already does successfully: recording the submission.

The email notification workflow is then handled independently.

That means the reliability of the digest no longer depends on the old Wix Forms email automation correctly matching every individual submission.

Finding the Wix Data Collection ID

The backend code needs to know which Wix Data collection contains the form submissions. In this implementation, the collection ID was contact, so the query is:

const result = await wixData.query("contact");


contact is not a universal Wix collection name or a special Velo keyword. It is the collection ID used by this particular site’s form submission data.

When implementing the same approach on another Wix site, the collection ID may be different. One should therefore identify the collection containing the form submissions and use the appropriate collection ID.

The same applies to the field IDs used by the code. In this implementation they were:

submissionTime
firstName
lastName
email
subject
message


These IDs need to correspond to the actual fields in the site’s form submission collection.


Enabling Velo

To implement the new workflow, I enabled Dev Mode in the Wix editor.

Once enabled, the editor exposed the backend and public code areas.

Under Backend, I created a web module initially, but that turned out not to be what was needed for the weekly digest.

The relevant option was:

Add scheduled jobs


This created a jobs.config file.

The scheduled job is what allows Wix to execute backend code automatically without somebody visiting the website or manually running anything.


Creating the Backend Function

I created a backend JavaScript file called:

weeklyDigest.js


The function was named:

sendWeeklyDigest


The basic job of the function is:

  1. Determine the beginning of the digest period.
  2. Query the Wix Data collection containing the form submissions.
  3. Sort the submissions from newest to oldest.
  4. Format each submission into readable text.
  5. Create a message containing all enquiries.
  6. Send the message using a Wix Triggered Email.
  7. Send a zero-enquiry message if there were no submissions.

The Wix Data collection used by the form contained the following relevant fields:

submissionTime
firstName
lastName
email
subject
message


The backend code queries the collection using the submissionTime field.


Testing the Data Query First

Before worrying about email delivery, I tested whether the backend function could actually retrieve the form submissions.

This was an important step because it separated the two problems.

The function successfully returned the stored submissions, including information such as:

date
firstName
lastName
email
subject
message


That confirmed that Velo could access the form data directly.

It also meant that the weekly digest did not need to interact with the old Wix Forms automation at all.


Creating the Triggered Email

The next step was to create a new email under Triggered Emails.

The email was given a unique Email ID, which Wix provided for use in backend code.

The email template contained a dynamic variable:

${enquiries}


The backend function passes the formatted enquiry text to this variable.

This is useful because the email design itself remains in Wix, while the backend code controls the actual enquiry content.

The final flow is therefore:

weeklyDigest.js
↓
triggeredEmails.emailContact(...)
↓
Triggered Email template
↓
${enquiries}


Testing with a Real Submission

For the initial test, the Triggered Email was configured to send to a test contact.

This made it possible to run the backend function manually and inspect the actual email before changing the production recipient.

The first successful test produced an email containing the enquiry information.

This was the point where the new workflow was proven to work independently of the original Wix Forms automation.


Testing a Large Number of Enquiries

Testing with a single enquiry isn’t enough for a digest.

A weekly digest might contain many submissions, so I tested the email using a much larger set of real submissions.

The test retrieved more than twenty enquiries.

Initially, the first group displayed correctly, but the final enquiry extended outside the intended email area.

The problem was not the data query or the JavaScript.

The issue was the available space around the dynamic text element in the Wix email template.

Increasing the bottom padding of the relevant email section resolved the problem.

This was preferable to changing the data format or introducing HTML into the dynamic variable because the original plain-text approach was already rendering the enquiry information correctly.

After increasing the padding, the larger digest displayed correctly.


Testing a Single Enquiry

After testing a large digest, I also tested the opposite case.

The date range was temporarily reduced so that only a very small number of submissions were returned.

The email still displayed correctly.

This confirmed that increasing the available space did not cause the email to become excessively large when there were only a few enquiries.

Testing both extremes was useful:

Many enquiries → no overflow
Few enquiries → no excessive empty layout


Handling the Zero-Enquiry Case

There was one more case worth handling explicitly.

What should happen if a particular week contains no enquiries?

The original implementation simply stopped when the query returned zero results:


No submissions. Digest not sent.


Although this saves an unnecessary email, there is a practical argument for sending a weekly status message anyway.

If a recipient becomes accustomed to receiving a digest every week, suddenly receiving nothing could make it unclear whether:

  • there were no enquiries,
  • the automation failed,
  • the scheduled job did not run,
  • or the email was lost.

For that reason, I decided that the digest should be sent every week, even when there are no enquiries.

The zero-enquiry message is:


No website enquiries were received during this period.

Total enquiries: 0


This gives the recipient a clear confirmation that the weekly process ran successfully and that there simply weren’t any submissions.


Formatting the Enquiries

Each enquiry is formatted individually.

The digest includes:


1. 1 Sep 2026, 10:05 am
   Name: Example Person
   Email: example@example.com
   Subject: Example enquiry
   Message: The enquiry text goes here.


Each enquiry is separated from the next using a horizontal text separator.

The labels were also given bold Unicode characters:


𝐍𝐚𝐦𝐞:
𝐄𝐦𝐚𝐢𝐥:
𝐒𝐮𝐛𝐣𝐞𝐜𝐭:
𝐌𝐞𝐬𝐬𝐚𝐠𝐞:


This approach was deliberately chosen instead of injecting HTML into the ${enquiries} variable.

The dynamic variable is fundamentally a text field, so keeping the content as plain text avoids introducing another dependency that could affect the email’s layout.


Newest Enquiries First

The original query returned enquiries in chronological order, with the oldest submission first.

For a weekly operational digest, I decided that newest-first was more useful.

The query therefore uses:

.descending("submissionTime")


The resulting digest looks like:

1. Most recent enquiry

2. Previous enquiry

3. Older enquiry

4. Oldest enquiry


This means the recipient immediately sees the newest enquiry when opening the email.


The Final Backend Code

The final backend function is structured like this:

import { triggeredEmails, contacts } from "wix-crm-backend";
import wixData from "wix-data";

export async function sendWeeklyDigest() {
  // ==================================================
  // DATE RANGE: Look back 7 days
  // ==================================================

  const startDate = new Date();

  startDate.setDate(startDate.getDate() - 7);

  console.log(
    `Digest period: ${startDate.toISOString()}${new Date().toISOString()}`,
  );

  // ==================================================
  // GET WEBSITE SUBMISSIONS
  // ==================================================

  const result = await wixData
    .query("contact")
    .ge("submissionTime", startDate)
    .descending("submissionTime")
    .find();

  console.log(`Found ${result.items.length} submissions.`);

  // ==================================================
  // NO ENQUIRIES
  // ==================================================

  if (result.items.length === 0) {
    console.log("No submissions found.");

    const enquiries =
      "No website enquiries were received during this period.\n\n" +
      "Total enquiries: 0";

    const contactResult = await contacts
      .queryContacts()
      .eq("primaryInfo.email", "RECIPIENT_EMAIL")
      .find();

    if (contactResult.items.length === 0) {
      console.log("Recipient contact not found.");
      return;
    }

    const contactId = contactResult.items[0]._id;

    await triggeredEmails.emailContact("TRIGGERED_EMAIL_ID", contactId, {
      variables: {
        enquiries,
      },
    });

    console.log("Zero-enquiry digest sent.");
    return;
  }

  // ==================================================
  // FORMAT ENQUIRIES
  // ==================================================

  const enquiries = result.items
    .map((item, index) => {
      const date = new Date(item.submissionTime);

      return `${index + 1}. ${date.toLocaleString("en-AU", {
        dateStyle: "medium",
        timeStyle: "short",
        timeZone: "Australia/Sydney",
      })}
𝐍𝐚𝐦𝐞: ${item.firstName || ""} ${item.lastName || ""}
𝐄𝐦𝐚𝐢𝐥: ${item.email || ""}
𝐒𝐮𝐛𝐣𝐞𝐜𝐭: ${item.subject || ""}
𝐌𝐞𝐬𝐬𝐚𝐠𝐞: ${item.message || ""}`;
    })
    .join("\n\n------------------------------\n\n");

  // ==================================================
  // GET RECIPIENT
  // ==================================================

  const contactResult = await contacts
    .queryContacts()
    .eq("primaryInfo.email", "RECIPIENT_EMAIL")
    .find();

  if (contactResult.items.length === 0) {
    console.log("Recipient contact not found.");
    return;
  }

  const contactId = contactResult.items[0]._id;

  // ==================================================
  // SEND DIGEST
  // ==================================================

  await triggeredEmails.emailContact("TRIGGERED_EMAIL_ID", contactId, {
    variables: {
      enquiries,
    },
  });

  console.log("Weekly digest sent.");
}


The collection ID, recipient address and Triggered Email ID in this example should of course be replaced with the values used by the individual website.


Scheduling the Weekly Digest

The final piece was the scheduled job.

The jobs.config file defines which backend function should run and when.

The configuration used for the weekly digest was:

{
  "jobs": [
    {
      "functionLocation": "/weeklyDigest.js",
      "functionName": "sendWeeklyDigest",
      "description": "Send weekly contact form digest",
      "executionConfig": {
        "cronExpression": "0 19 * * 0"
      }
    }
  ]
}


The cron expression is:

0 19 * * 0


Wix scheduled jobs use UTC, so this corresponds to Sunday evening UTC.

In an Australian location that observes daylight saving, this means the job runs on Monday morning locally, with the local clock time shifting by an hour when daylight saving begins or ends.

That is actually useful here because the important requirement is that the job always runs on Monday, rather than trying to make the code calculate whether it is currently Sunday, Monday, the end of a month, or the start of a new year.

The backend function simply looks back seven days from the time it runs.


Why a Rolling Seven-Day Window Is Sufficient

One concern with a weekly digest is accidentally making the date calculation too complicated.

It would be possible to write code that determines:

  • the current day of the week;
  • the previous Sunday;
  • the previous Monday;
  • the start of the previous calendar week;
  • the end of the previous calendar week;
  • month boundaries;
  • year boundaries;
  • daylight-saving changes.

For this particular use case, none of that is necessary.

The scheduled job itself establishes the cadence.

If it runs once every Monday, the backend function can simply use:

const startDate = new Date();
startDate.setDate(startDate.getDate() - 7);


and query submissions from that point until the current execution time.

The advantage is that the same code continues to work when the date crosses:

month → month
year → year
February → March
December → January


without requiring special calendar logic.

Daylight saving also does not require special handling in the query because the submission timestamps are stored by Wix and the display conversion is explicitly made using:

timeZone: "Australia/Sydney";


Avoiding Duplicate Digests

There is one subtle distinction worth documenting.

A seven-day rolling window is appropriate when the function is executed once a week by the scheduled job.

However, if the function is manually run twice on the same day, both executions will retrieve the same submissions.

For example:

Monday 9:00 AM
    ↓
submissions from previous 7 days
    ↓
Digest A

Monday 9:05 AM
    ↓
same previous 7 days
    ↓
Digest B


The scheduled job does not have this problem during normal operation because it executes once per week.

However, manually testing the function is different.

For this reason, the function should be treated as a scheduled weekly process, rather than something intended to be manually executed repeatedly in production.

If duplicate protection becomes a requirement in the future, the system could be extended with a stored record of the last successfully processed period. That would make the workflow more robust against repeated manual execution, but it also introduces additional state and complexity.

For a simple weekly digest, the seven-day query is a reasonable balance between reliability and maintainability.


The Final Workflow

The completed system is considerably simpler than the original arrangement:

Visitor
   ↓
Wix Contact Form
   ↓
Submission stored in Wix Data
   ↓
Weekly scheduled job
   ↓
weeklyDigest.js
   ↓
Query submissions from previous 7 days
   ↓
Sort newest → oldest
   ↓
Format enquiries
   ↓
Send Triggered Email
   ↓
Recipient receives one weekly digest


If there are submissions:

Found 12 submissions.
        ↓
Create digest containing 12 enquiries.
        ↓
Send email.


If there are no submissions:

Found 0 submissions.
        ↓
Create zero-enquiry message.
        ↓
Send email confirming 0 enquiries.


This is fundamentally different from the old workflow.

The old notification workflow depended on each individual form submission successfully passing the criteria of an app-controlled automation trigger.

The new email workflow depends only on the submission being stored successfully in Wix Data. The Wix Form itself is not being replaced; it continues to collect and store the submissions.

That makes the system much easier to understand and troubleshoot.


Troubleshooting the New Setup

If the digest stops arriving, there are now several clear places to check.

1. Check whether the form submission exists

First confirm that the enquiry appears in the Wix submissions/contact data.

If it exists there, the form itself is working.

2. Check the scheduled job

Open the site’s backend configuration and verify that jobs.config still points to:

/weeklyDigest.js


and:

sendWeeklyDigest


3. Check the backend logs

Run the function manually while testing and look for:

Digest period: ...
Found X submissions.


If the number is correct, the data query is working.

4. Check the recipient contact

The backend code searches the Wix Contacts database for the configured recipient email address.

If Wix cannot find that contact, the function will report:

Recipient contact not found.


5. Check the Triggered Email

The Triggered Email ID used in the code must correspond to the campaign containing the ${enquiries} variable.

If the campaign is deleted or replaced, the backend code would need to be updated with the new ID.

6. Check the email’s spam or promotional folders

During testing, the generated email may not initially appear in the primary inbox.

Email providers can classify automated Wix emails as promotional or otherwise filter them differently from ordinary personal email.

This does not necessarily indicate that the Wix function failed.


What I Would Do Differently Next Time

The biggest lesson from this exercise is that a successful form submission does not necessarily mean a successful downstream automation.

The original notification system looked deceptively simple:

Form → Automation → Email


but the automation trigger was tied to an older Wix Forms implementation and could not be freely edited. When individual submissions were skipped because their data did not satisfy the trigger filters, there was no straightforward way to repair the workflow from the automation interface.

The more transparent architecture for the email process is:

Form → Data → Scheduled backend code → Email digest


The additional JavaScript may look more complicated initially, but every part of the process is visible and controllable.

The date range is explicit.

The database query is explicit.

The sorting order is explicit.

The recipient is explicit.

The email campaign is explicit.

And the zero-enquiry behaviour is explicit.

That makes future troubleshooting substantially easier.


Final Configuration

The finished solution consists of three main pieces, while the existing form remains in place as the source of the stored submissions.

Wix Form

The existing form continues to collect enquiries normally.

No dependency on the old email notification automation is required.

weeklyDigest.js

The backend function:

  • queries the form submission collection;
  • looks back seven days;
  • sorts submissions newest first;
  • formats the enquiry details;
  • sends one digest;
  • sends a zero-enquiry notification when there are no submissions.

jobs.config

The scheduled job runs the backend function once per week.

The job itself determines the weekly cadence, while the backend function simply looks back seven days.

This separation keeps the implementation relatively simple and means there is no need to build complicated calendar calculations into the JavaScript.

The result is a small but useful example of where Velo can be preferable to relying entirely on an app-provided Wix automation: when the built-in workflow becomes difficult to inspect or modify, moving the logic into a backend function can make the entire process much more predictable.


Consider subscribing to my YouTube channel & follow me on X(Twitter).

Share this article if you found it useful!