How to Send Appointment Reminder Emails from a MySQL Booking Table

Build a Spojit workflow that wakes up every morning, reads tomorrow's bookings from your MySQL database, and sends each customer a personalized reminder email through Resend from your own verified domain.

What This Integration Does

No-shows are expensive, and most of them come down to people simply forgetting. If your bookings already live in a MySQL table, you have everything you need to remind customers automatically. This workflow queries upcoming appointments once a day and sends a tailored email to each attendee, with their name, appointment time, and any other detail you store. Because it sends through the Resend connector rather than Spojit's built-in mail service, the messages come from your own domain, land in the inbox looking like they came straight from your business, and do not count against your Spojit monthly email allowance.

The workflow is driven by a Schedule trigger on a daily cron expression. On each run it executes one read-only SQL query against MySQL to pull the day's reminder list, loops over the returned rows, and calls Resend's send-email tool once per booking. It writes nothing back to your database in the basic version, so re-running it is safe except that customers would receive a second email. To make reminders idempotent you can add an update-rows step that flags each row as reminded and exclude flagged rows from the query, which is covered in the steps below.

Prerequisites

  • A MySQL connection in Spojit with read access to your bookings table. See the MySQL connector article for setup.
  • A Resend connection with a verified sending domain and an API key. See the Resend connector article.
  • A bookings table that includes, at minimum, a customer email, a customer name, and an appointment date/time column.
  • Knowing the exact column names and date/time format your table uses, plus the IANA timezone your business runs in (for example Australia/Sydney).

Step 1: Add a Schedule trigger

Create a new workflow in the Workflow Designer and set the trigger type to Schedule. Add a 5-field Unix cron expression and an IANA timezone. To run every morning at 7:00 AM Sydney time, use:

0 7 * * *
Australia/Sydney

A single Schedule trigger can hold multiple schedules if you want, for example a second send for a different region. The trigger output is { scheduledAt }, which you can reference downstream as {{ trigger.scheduledAt }}.

Step 2: Compute tomorrow's date

Add a Connector node in Direct mode using the date utility connector and the add tool. Add one day to the run time so you remind customers the day before their appointment. Set the input to the trigger time and the amount to one day, then take the date-only part. Bind the result to a variable such as reminderDate. If your appointment column already stores a plain date you will compare against {{ reminderDate }} in the next step; if it stores a full timestamp, you will compare against a date range instead.

Step 3: Query upcoming bookings from MySQL

Add a Connector node in Direct mode, choose the MySQL connector, and select the execute-query tool. Put your SELECT statement in the query field and pass values through the params array so they are bound safely rather than concatenated into the SQL. Bind the result to bookings.

SELECT customer_name, customer_email, appointment_at, service_name
FROM bookings
WHERE DATE(appointment_at) = ?
  AND status = 'confirmed'
  AND reminder_sent = 0

Set params to [ "{{ reminderDate }}" ] so the ? placeholder is filled with tomorrow's date. The reminder_sent = 0 condition is what keeps the workflow idempotent once you add Step 6; omit that line for the basic version. Keep this query read-only here so a re-run never changes data unexpectedly.

Step 4: Loop over the returned rows

Add a Loop node in ForEach mode and point it at the rows returned by the query, for example {{ bookings.rows }} (use the row collection your query result exposes). Each iteration makes the current booking available as {{ item }}, giving you {{ item.customer_name }}, {{ item.customer_email }}, {{ item.appointment_at }}, and any other selected column. All of the email-sending logic goes inside the loop body so it runs once per customer.

Step 5: Send the reminder through Resend

Inside the loop, add a Connector node in Direct mode, choose the Resend connector, and select the send-email tool. Map the fields:

  • to: {{ item.customer_email }}
  • from: a verified address on your domain, for example Bookings <bookings@yourdomain.com>. If you leave from blank, Resend uses the default sender configured on the connection.
  • subject: Reminder: your {{ item.service_name }} appointment tomorrow
  • html or text: your message body, personalized with the booking variables.
  • reply_to (optional): a monitored inbox so replies reach your team.

A simple HTML body might read:

<p>Hi {{ item.customer_name }},</p>
<p>This is a friendly reminder about your {{ item.service_name }}
appointment on {{ item.appointment_at }}.</p>
<p>If you need to reschedule, just reply to this email.</p>

If you want a nicely formatted local time instead of the raw column value, add a date connector format step inside the loop before this node and reference its output in the body.

Step 6: Mark the booking as reminded (optional but recommended)

To guarantee each customer gets exactly one reminder even if the workflow runs twice, add a Connector node in Direct mode after the send, using the MySQL connector and the update-rows tool. Update the current booking's row to set reminder_sent = 1, keyed on its primary id (select that id column in your Step 3 query so it is available as {{ item.id }}). With the reminder_sent = 0 filter already in the Step 3 query, reminded rows drop out of future runs automatically. Place this step last in the loop body so a row is only flagged after its email actually went out.

Tips

  • Always pass dynamic values through the MySQL params array rather than building the SQL string by hand. It is safer and avoids quoting bugs.
  • For large daily volumes, Resend rate limits apply. If you reminder hundreds of customers per run, consider the send-batch-emails tool to send many messages in one call instead of one send-email per loop iteration.
  • Ask Miraxa, the intelligent layer across your automation, to scaffold this for you with a prompt like: "Add a Loop node over {{ bookings.rows }} and inside it a Resend send-email node mapping to to {{ item.customer_email }}." Then fine-tune the body in the properties panel.
  • Use a clear, branded subject and a real reply_to so reminders feel personal and customers can respond.

Common Pitfalls

  • Timezone drift. The Schedule timezone, your database timezone, and the appointment_at values must agree. If appointments are stored in UTC, compute the date range in UTC in your query rather than comparing against a local date.
  • Unverified domain. Resend will refuse to send from a domain that is not verified. Confirm verification (the verify-domain tool can help) before turning the schedule on.
  • Duplicate emails on re-run. Without the reminder_sent flag from Step 6, manually re-running the workflow sends every customer a second reminder. Add the flag before going live.
  • Empty result set. On days with no bookings the query returns no rows and the loop simply does nothing, which is fine. Do not add a step that assumes at least one row exists.

Testing

Before scheduling, validate on a tiny scope. Temporarily change the Step 3 query to target a single test booking whose email you control (for example add AND customer_email = 'you@yourdomain.com'), then use the Run button to execute the workflow manually. Check that the loop runs once, that the Resend step reports success, and that the email arrives from your domain with the variables filled in correctly. Inspect the run in your execution history to confirm the SQL returned what you expected. Once the single-row test looks right, remove the test filter, confirm the reminder_sent logic, and enable the schedule.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.