You can use a Pub/Consumer model with a message queue. It will make your solution more robust and distributed. I always try not to use sleep in any kind of distributed environment.
Your system will have 3 components:
Producer: This will take care of scheduling and writing messages to a message queue.
Message Queue: You can use RabbitMQ(with delayMessageExchange) or ActiveMQ(it has in-built scheduling). Your message queue will take care of the scheduling problem. Because the message will be delivered only after a specific time delay to the consumer.
Consumer: The consumer will read data from the message queue and perform operations as the functionality
HLD will look like this:

You can use https://pkg.go.dev/github.com/streadway/amqp this package for RabbitMQ
and https://pkg.go.dev/github.com/go-stomp/stomp this package for ActiveMQ
Here is an simple example, I have used AWS ActiveMQ.
package main
import (
"crypto/tls"
"flag"
"fmt"
"github.com/go-stomp/stomp/v3"
"os"
"time"
)
const defaultPort = ":61613"
var serverAddr = flag.String("server", "b-50ad5529-0347-4308-af59-f6265d68d290-1.mq.us-east-1.amazonaws.com:61614", "STOMP server endpoint")
var messageCount = flag.Int("count", 2, "Number of messages to send/receive")
var queueName = flag.String("queue", "/queue/client_test", "Destination queue")
var helpFlag = flag.Bool("help", false, "Print help text")
var stop = make(chan bool)
// these are the default options that work with RabbitMQ
var options []func(*stomp.Conn) error = []func(*stomp.Conn) error{
stomp.ConnOpt.Login("activemquser", "activemqpassword"),
}
func main() {
flag.Parse()
if *helpFlag {
fmt.Fprintf(os.Stderr, "Usage of %s\n", os.Args[0])
flag.PrintDefaults()
os.Exit(1)
}
subscribed := make(chan bool)
go recvMessages(subscribed)
// wait until we know the receiver has subscribed
<-subscribed
go sendMessages()
<-stop
<-stop
}
func sendMessages() {
defer func() {
stop <- true
}()
netConn, err := tls.Dial("tcp", *serverAddr, &tls.Config{})
if err != nil {
println("cannot connect to server", err.Error())
}
conn, err := stomp.Connect(netConn, options...)
if err != nil {
println("cannot connect to server", err.Error())
}
for i := 1; i <= *messageCount; i++ {
text := fmt.Sprintf("Message #%d", i)
fmt.Println("sending message ", text, " ", time.Now())
// scheduling a message with 15 seconds delay
err = conn.Send(*queueName, "text/plain",
[]byte(text), stomp.SendOpt.Header("AMQ_SCHEDULED_DELAY", "15000"))
if err != nil {
println("failed to send to server", err)
return
}
// schedule each message after 3 secs
time.Sleep(3 * time.Second)
}
println("sender finished")
}
func recvMessages(subscribed chan bool) {
defer func() {
stop <- true
}()
netConn, err := tls.Dial("tcp", *serverAddr, &tls.Config{})
if err != nil {
println("cannot connect to server", err.Error())
}
conn, err := stomp.Connect(netConn, options...)
if err != nil {
println("cannot connect to server", err.Error())
}
sub, err := conn.Subscribe(*queueName, stomp.AckAuto)
if err != nil {
println("cannot subscribe to", *queueName, err.Error())
return
}
close(subscribed)
for i := 1; i <= *messageCount; i++ {
msg := <-sub.C
expectedText := fmt.Sprintf("Message #%d", i)
actualText := string(msg.Body)
fmt.Println("got message", actualText, " ", time.Now())
if expectedText != actualText {
println("Expected:", expectedText)
println("Actual:", actualText)
}
}
println("receiver finished")
}