How to Run a Daily MySQL Data-Quality Audit with AI
Build a Spojit workflow that runs every morning on a schedule, pulls suspect rows from MySQL, asks an Agent-mode connector to judge and summarize the data-quality issues against a fixed response schema, and posts a tidy digest to Slack.
What This Integration Does
Bad data creeps into operational databases quietly: customers with no email, orders stuck in limbo, prices that went negative, duplicate records, timestamps from the future. Catching these by hand means someone remembers to run a query, eyeballs the output, and decides what matters. This workflow does that scan for you on a fixed daily schedule, then uses an Agent-mode Connector node to triage the findings and write a human-readable summary so your team starts the day knowing exactly what is wrong and how bad it is.
The workflow runs on a Schedule trigger. On each run it executes one or more diagnostic queries against MySQL with the MySQL connector, packages the raw rows into a compact payload with the JSON connector, hands that payload to a Connector node in Agent mode that returns a structured verdict, and sends the verdict to a Slack channel with the Slack connector. It is read-only against your database: it never writes back, so re-running it is always safe. Each run is independent and stateless, producing one Slack digest reflecting the data at the moment it ran.
Prerequisites
- A MySQL connection in Spojit with read access to the tables you want to audit. See Adding a New Connection.
- A Slack connection authorized to post to your target channel, and the channel ID or name (for example
#data-quality). - Knowledge of your schema: the table names, the columns that should never be null, and any business rules (for example "an order over 30 days old should not still be
pending"). - An AI model available in your workspace for the Agent-mode step, which spends AI credits per run.
Step 1: Start with a Schedule trigger
Create a new workflow in the Spojit designer and set the trigger to Schedule. Add a 5-field Unix cron expression and an IANA timezone so the audit runs at a predictable local time. To run at 7:00 AM on weekdays in Sydney, use:
Cron: 0 7 * * 1-5
Timezone: Australia/Sydney
The trigger output is { scheduledAt }, which you can reference downstream as {{ trigger.scheduledAt }} to stamp the digest with the run time. A single Schedule trigger can hold several schedules if you want, say, an extra midday pass.
Step 2: Query MySQL for suspect records
Add a Connector node in Direct mode, choose your MySQL connection, and select the execute-query tool. Write a read-only SELECT that surfaces the rows you consider suspect. Keep it bounded with a LIMIT so a bad day cannot flood the audit. For example, to find customers missing contact details or with malformed data:
SELECT id, name, email, phone, created_at
FROM customers
WHERE email IS NULL
OR email NOT LIKE '%@%'
OR phone IS NULL
OR created_at > NOW()
LIMIT 200
Bind the result to an output variable such as suspectCustomers. The tool returns the matched rows plus a row count, so you can reference the rows as {{ suspectCustomers.rows }}. If you are unsure of column names, run a one-off describe-table call first to confirm the schema before writing the audit query.
Step 3: Audit a second dimension (optional second query)
Most audits check more than one rule. Add another Direct-mode MySQL execute-query node for a different problem class, for example stale orders:
SELECT id, status, total, updated_at
FROM orders
WHERE status = 'pending'
AND updated_at < NOW() - INTERVAL 30 DAY
LIMIT 200
Bind it to staleOrders. If you have several independent checks, wrap them in a Parallel node so each query runs in its own branch concurrently and the run finishes faster. Each branch is a separate MySQL execute-query step with its own output variable.
Step 4: Combine the findings into one payload
Add a Connector node in Direct mode using the JSON connector. Use the stringify tool to fold the query results into a single compact JSON string that the AI step can read in one pass. Pass an object that bundles each check and its count, for example:
{
"auditedAt": "{{ trigger.scheduledAt }}",
"customersMissingContact": {{ suspectCustomers.rows }},
"stalePendingOrders": {{ staleOrders.rows }}
}
Bind the output to auditPayload. Keeping the structure explicit (one named key per check) helps the AI attribute every issue to the right rule. If your rows are deeply nested, the JSON connector's pick or flatten tools can trim them to just the fields the audit cares about before stringifying, which keeps AI input cost down.
Step 5: Flag and summarize with an Agent-mode connector and a response schema
Add a Connector node and switch it to Agent mode. This is where the agent reviews the findings, decides severity, and writes the summary. Give it a clear prompt and turn on the Response Schema so the output is reliable JSON you can route into Slack without guessing. A prompt like this works well:
You are auditing daily database health. The payload below lists
suspect records found by SQL checks, grouped by rule.
For each group, judge whether the issues are critical, warning,
or informational, and write a one-line summary a data team can act on.
Then give an overall verdict for the day.
Payload:
{{ auditPayload }}
Define the Response Schema so every run returns the same shape:
{
"type": "object",
"properties": {
"overallSeverity": { "type": "string", "enum": ["critical","warning","ok"] },
"headline": { "type": "string" },
"issues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"rule": { "type": "string" },
"severity": { "type": "string" },
"count": { "type": "number" },
"summary": { "type": "string" }
}
}
}
}
}
Bind the result to auditVerdict. Because the schema forces structured output, you can reference fields directly downstream, such as {{ auditVerdict.overallSeverity }} and {{ auditVerdict.headline }}.
Step 6: Post the digest to Slack
Add a Connector node in Direct mode, choose your Slack connection, and select the send-message tool. Set the channel and build a message from the verdict so the team sees the headline first and the per-rule breakdown below it. For example, set the message text to:
Daily data-quality audit ({{ trigger.scheduledAt }})
Status: {{ auditVerdict.overallSeverity }}
{{ auditVerdict.headline }}
To list each issue on its own line, place a Loop node in ForEach mode over {{ auditVerdict.issues }} before this step and either build a combined string with the Transform node or send one send-message per issue inside the loop. For a single tidy post, build the full body in a Transform node first, then pass it to one send-message call.
Step 7: Skip quiet days (optional)
To avoid posting "all clear" noise, add a Condition node before the Slack step that checks {{ auditVerdict.overallSeverity }}. Route the true branch (severity is warning or critical) to the Slack send-message node, and leave the false branch empty so clean days stay silent. You can also branch on severity to post critical findings to a separate, higher-visibility channel.
Tips
- Always cap audit queries with a
LIMIT. A schema change or import bug can suddenly match thousands of rows, and you do not want all of them flowing into the AI step. - Trim rows to only the columns the audit needs (use JSON
pick) before the Agent-mode step. Smaller payloads mean fewer AI credits per run and faster summaries. - Ask Miraxa to scaffold the workflow: "Build a workflow on a Schedule trigger that runs a MySQL
execute-query, sends the rows to an Agent-mode connector with a response schema, and posts the result to Slack." Then fine-tune each node in the properties panel. - Keep one query per problem class. Separate, well-named checks make the AI summary easier to attribute than one giant query that mixes rules.
Common Pitfalls
- Cron is evaluated in the timezone you set, not the server's. If the audit fires at the wrong hour, check the IANA timezone on the Schedule trigger first.
- If your schema changes, an audit query referencing a renamed or dropped column will fail the whole run. Confirm columns with
describe-tablewhen you build, and watch for failures after migrations. - Without a Response Schema, the Agent-mode output is free-form text and the Slack templating against
{{ auditVerdict.headline }}will not resolve. Define the schema so the shape is guaranteed. - The
send-messagetool needs the Slack connection authorized for the target channel. A private channel the connection has not joined will reject the post.
Testing
Before enabling the schedule, run the workflow once with the Run button while pointing the queries at a narrow window (for example add AND created_at > NOW() - INTERVAL 1 DAY) so only a handful of rows return. Confirm in the execution log that the MySQL step returned the expected rows, that the Agent-mode step produced JSON matching your schema, and that the Slack post arrived with the headline and per-rule lines. Once a small run looks right, remove the narrowing clause, enable the workflow, and let the daily schedule take over.