API: /api/dispatch
Relevant source files
The following files were used as context for generating this wiki page:Introduction
The /api/dispatch endpoint is a serverless function that acts as a scheduled task handler for the Heyme application. Its primary responsibility is to send out email notifications for video messages that are due on the current day. The endpoint queries the MySQL database, identifies all videos scheduled for delivery, constructs personalized emails using a template, and sends them to the designated recipients via the Zeptomail transactional email service.
This process is central to the application's core feature of "sending messages to your future self." The endpoint is also wrapped with Highlight.io for monitoring and error tracking in production.
Sources: pages/api/dispatch.ts, README.md
Core Logic and Workflow
The handleDispatchEmails function contains the main operational logic. Upon invocation, it connects to the database, fetches due videos, processes each one, and then closes the connection.
Sources: pages/api/dispatch.ts:35-100
The following diagram illustrates the complete workflow of the dispatch process.
This diagram shows the step-by-step logic executed by the handleDispatchEmails function.
Sources: pages/api/dispatch.ts:35-100
Database Interaction
The API uses the knex library to interact with the project's MySQL database. Two key database operations are performed during the dispatch process.
Sources: pages/api/dispatch.ts, package.json
fetchAllVideosForToday
This function retrieves all video records from the Videos table where the send_at column matches the current date.
// pages/api/dispatch.ts:24-27
export async function fetchAllVideosForToday(connection: Knex): Promise<any[]> {
return connection("Videos")
.select("*")
.where({ send_at: dayjs().format('YYYY-MM-DD') })
}
updateVideoToSent
After an email has been successfully sent for a video, this function is called to update the record in the Videos table. It sets the sent flag to 1 to prevent duplicate emails.
// pages/api/dispatch.ts:29-33
export async function updateVideoToSent(connection: Knex, id: number) {
await connection("Videos").where({ id }).update({
sent: 1,
})
}
Inferred Videos Table Schema
Based on the queries and data handling in dispatch.ts and watch/[id].tsx, the Videos table likely has the following structure:
| Column | Type | Description |
|---|---|---|
id | INT / NUMBER | Primary key for the video record. |
video_url | VARCHAR / TEXT | The URL to the stored video file. |
email | VARCHAR | The recipient's email address. |
send_at | DATE | The scheduled date for the message delivery. |
send_to | VARCHAR | Indicates the recipient ("yourself" or other). |
created_at | DATETIME | Timestamp of when the video was created. |
sent | BOOLEAN / TINYINT | A flag (0 or 1) to indicate if the email has been dispatched. |
Sources: pages/api/dispatch.ts:24-33, 58-68, pages/watch/[id].tsx:10-16
Email Generation and Delivery
Email dispatch is handled by the zeptomail npm package. The process involves initializing the client, reading an HTML template, replacing placeholders with dynamic content, and sending the final email.
Sources: pages/api/dispatch.ts:8-21, 51-98, package.json
Email Sending Sequence
This sequence diagram shows the interaction between the API, the database, and the Zeptomail service.
This diagram illustrates the sequence of calls for processing each video.
Sources: pages/api/dispatch.ts:43-99
getEmailContent
This helper function reads the email.html template and replaces placeholders with dynamic data for each specific message.
| Placeholder | Replaced With | Source |
|---|---|---|
{{REPLACE_WATCH_LINK_HERE}} | A unique link to the watch page, e.g., https://heyme.io/watch/123. | pages/api/dispatch.ts:15, 87 |
{{REPLACE_GREETING_HERE}} | A dynamic greeting based on who sent the message. | pages/api/dispatch.ts:16, 88-91 |
{{REPLACE_BODY_HERE}} | A static body text explaining the service. | pages/api/dispatch.ts:17, 92 |
{{REPLACE_OUTRO_HERE}} | A static closing message. | pages/api/dispatch.ts:18, 93 |
Sources: pages/api/dispatch.ts:11-21, public/mails/email.html
The subject line and greeting are customized based on whether the send_to field is "yourself", providing a more personal touch.
// pages/api/dispatch.ts:83-91
subject: isYourself
? `You sent yourself a message from the past. Check it out.`
: `Someone sent you a message from the past. Check it out.`,
htmlbody: getEmailContent({
watchLink: `${process.env.SITE_URL}/watch/${video.id}`,
greeting: isYourself
? `Do you still remember sending yourself a message on ${video.created_at}?`
: `Someone sent you a message on ${video.created_at}`,
// ...
})
Testing
A unit test exists for the getEmailContent function to ensure its reliability. The test verifies that all placeholders in the email template are correctly replaced with the provided data.
Sources: tests/api/dispatch.spec.ts
// tests/api/dispatch.spec.ts:6-23
test('the getEmailContent function returns content with the greeting, outro, body and subject correctly replaced', async () => {
// arrange
const greeting = "Hello"
const body = "This is the body"
const outro = "Bye"
const watchLink = "https://example.com"
//act
const content = await getEmailContent({
greeting,
body,
outro,
watchLink,
})
// assert
expect(content).toContain(greeting)
expect(content).toContain(body)
expect(content).toContain(outro)
expect(content).toContain(watchLink)
})