Overview
The Message Scheduling feature allows you to create, update, and manage messages that will be sent to contacts after a specified number of days. This is useful for automated follow-ups, reminders, and scheduled communications.
Key Features
Scheduled Delivery Messages are sent after a configurable delay in days
Contact Selection Select recipients from your People list
Full CRUD Operations Create, read, update, and delete scheduled messages
Validation Built-in validation for phone numbers and scheduling rules
Message Data Structure
Messages contain the following fields:
interface Message {
id : string ;
content : string ;
sendToPhone : string ;
sendAfter : number ; // Days to wait before sending
sendTo : {
name : string ;
};
createdAt : Date ;
}
Creating a Message
Open the Add Message Dialog
Click the “Add Message” button at the bottom of the Messages section to open the creation form. < Dialog >
< DialogTrigger asChild >
< Button className = "w-full" >
< MailPlus />
< span > Add Message </ span >
</ Button >
</ DialogTrigger >
< DialogContent >
< DialogTitle > Add Message </ DialogTitle >
< CreateMessageForm people = { people } />
</ DialogContent >
</ Dialog >
Fill in Message Details
The form contains three required fields:
Message : The content to send (textarea)
Send To : Select a contact from the dropdown
Send After : Number of days to wait before sending
< form action = { createAction } className = "space-y-3" >
< Label htmlFor = "content" > Message </ Label >
< Textarea name = "content" className = "h-32" />
< Label htmlFor = "sendToPhone" > Send To </ Label >
< Select name = "sendToPhone" >
< SelectTrigger >
< SelectValue placeholder = "Send To" />
</ SelectTrigger >
< SelectContent >
{ people . map (( person ) => (
< SelectItem key = { person . id } value = { person . phone } >
{ person . name } ( { person . phone } )
</ SelectItem >
)) }
</ SelectContent >
</ Select >
< Label htmlFor = "sendAfter" > Send After (in days) </ Label >
< Input type = "number" step = { 0.0001 } min = { 0 } name = "sendAfter" />
< Button type = "submit" disabled = { createIsPending } >
Add
</ Button >
</ form >
Validation and Submission
When you submit the form, the data is validated using Zod schema before being sent to the backend.
Validation Rules
The application enforces strict validation rules for message creation:
Phone numbers must follow the Bangladesh format:
Pattern : 8801XXXXXXXXX (13 digits starting with 8801)
const messageSchema = z . object ({
content: z . string (). min ( 1 , "Content cannot be empty" ),
sendToPhone: z
. string ()
. regex ( / ^ 8801 \d {9} $ / , "Invalid phone number format. Must be 8801XXXXXXXXX" ),
sendAfter: z
. number ()
. min ( 1 , "Send after is required and minimum after 1 day" ),
});
Content Requirements
Message content cannot be empty
Must be a valid string with at least 1 character
Scheduling Requirements
The sendAfter field must be at least 1 day for new messages. When updating, the minimum is 0 days.
Updating a Message
Click on any message card to open the update dialog:
< Dialog >
< DialogTrigger asChild >
< Card className = "cursor-pointer" >
< CardHeader > { message . content . slice ( 0 , 100 ) } ... </ CardHeader >
< CardContent >
< CardDescription >
Send to: { message . sendTo . name } ( { message . sendToPhone } )
</ CardDescription >
< CardDescription >
Send after: { message . sendAfter } Days
</ CardDescription >
</ CardContent >
</ Card >
</ DialogTrigger >
< DialogContent >
< DialogTitle > Update Message </ DialogTitle >
< MessageUpdateForm message = { message } people = { people } />
</ DialogContent >
</ Dialog >
The update form is pre-populated with existing values:
< Input type = "text" name = "id" hidden defaultValue = { message . id } />
< Textarea name = "content" defaultValue = { message . content } />
< Select name = "sendToPhone" defaultValue = { message . sendToPhone } >
{ /* Options */ }
</ Select >
< Input name = "sendAfter" defaultValue = { message . sendAfter } />
Deleting a Message
Within the update dialog, there’s a separate delete button:
< form action = { deleteAction } className = "space-y-3" >
< Input type = "text" name = "id" hidden defaultValue = { message . id } />
< Button type = "submit" variant = "outline" disabled = { deleteIsPending } >
Delete
</ Button >
</ form >
Server Actions
All message operations use Next.js Server Actions for server-side processing:
Create Message Action
Update Message Action
Delete Message Action
"use server" ;
import { revalidatePath } from "next/cache" ;
import { z } from "zod" ;
const messageSchema = z . object ({
content: z . string (). min ( 1 , "Content cannot be empty" ),
sendToPhone: z
. string ()
. regex ( / ^ 8801 \\ d {9} $ / , "Invalid phone number format. Must be 8801XXXXXXXXX" ),
sendAfter: z
. number ()
. min ( 1 , "Send after is required and minimum after 1 day" ),
});
export default async function createMessage (
prevState : MessagePrevState ,
formData : FormData
) : Promise < MessagePrevState > {
const parsedData = messageSchema . safeParse ({
content: formData . get ( "content" ),
sendToPhone: formData . get ( "sendToPhone" ),
sendAfter: Number ( formData . get ( "sendAfter" )),
});
if ( ! parsedData . success ) {
return {
error: parsedData . error . errors . map (( err ) => err . message ). join ( ", " ),
};
}
const response = await fetch (
` ${ process . env . BACKEND_URL } /messages/create-one` ,
{
method: "POST" ,
headers: {
Authorization: `Basic ${ Buffer . from (
` ${ process . env . USERNAME } : ${ process . env . PASSWORD } `
). toString ( "base64" ) } ` ,
"Content-Type" : "application/json" ,
},
body: JSON . stringify ( parsedData . data ),
}
);
if ( ! response . ok ) {
return {
error: `Backend Error: ${ response . status } ${ response . statusText } ` ,
};
}
revalidatePath ( "/" );
return { success: true };
}
Toast Notifications
The application provides real-time feedback using toast notifications:
useEffect (() => {
if ( createState . success ) {
toast . success ( "Message created successfully" , {
richColors: true ,
});
}
if ( createState . error ) {
toast . error ( "Message creation failed" , {
description: createState . error ,
duration: 3500 ,
richColors: true ,
});
}
}, [ createState ]);
Backend API Endpoints
The frontend communicates with these backend endpoints:
Endpoint Method Purpose /messages/allGET Fetch all messages /messages/create-onePOST Create a new message /messages/update-one/:idPOST Update an existing message /messages/delete-one/:idPOST Delete a message
All endpoints require Basic Authentication using USERNAME and PASSWORD environment variables.