In my ever-evolving journey to being more consistent with my content, I created a new board in Notion that helps me schedule my content each day of the week. I tried it for a week and it helped be successful in my consistency goals for that week. I took it a step further and wrote code to automate the process a bit, so I want to talk about that in today’s post.

My weekly planner in Notion

I started by defining what my regular cadence would be with writing content. All of these are weekly goals, with the exception of Tweets per day:

  • 1 x blog post
  • 1 x YouTube video
  • 1 x YouTube short
  • 2 x Newsletter issues
  • 1 x Twitter space
  • 3 x Tweet min per day

I do try to repurpose some of my content. So for instance the weekly blog post gets re-used into one of the newsletter issues (and if you’re reading this in your email, this is also available on my blog, along with a bunch of other posts). I took all of these goals, broke them up into days of the week, and created a board like this in Notion:

So this was a great start, but its something like 27 cards I’d have to create every week to keep this up. I’m a hard worker, but want to be lazy where I can. So I wrote some code to generate these cards for me each week.

Automatically creating the cards

To automate the process of creating the cards, I wrote a small tool in Go to hook into the Notion API and create the cards. I started by defining a map that would contain a list of the cards to be created by day.

type ContentCard struct {
	Name string
	Icon string
	Date time.Time
	Type string
}

cards := map[string][]ContentCard{
	"Monday": {
		{
			Name: "Newsletter stats",
			Icon: "tweet",
			Type: "Tweet - Stats",
		},
		{
			Icon: "tweet",
			Type: "Tweet - Value",
		},
		{
			Icon: "tweet",
			Type: "Tweet - QQ/Engagement",
		},
	},
	"Tuesday": {
		{
			Name: "Blog post",
			Icon: "tweet",
			Type: "Tweet - Personal link",
		},
		{
			Icon: "tweet",
			Type: "Tweet - Value",
		},
		{
			Icon: "tweet",
			Type: "Tweet - QQ/Engagement",
		},
		{
			Icon: "newsletter",
			Type: "Newsletter - Blog post",
		},
		{
			Icon: "blog",
			Type: "Publish - Blog post",
		},
	},
	// removed the rest of the days for brevity...
}

I also had a map that would contain an arbitrary string key that corresponds with a public image used by the Notion icons.

var icons map[string]string = map[string]string{
	"tweet":      "https://www.notion.so/icons/duck_blue.svg",
	"blog":       "https://www.notion.so/icons/passport_purple.svg",
	"youtube":    "https://www.notion.so/icons/video-camera_red.svg",
	"newsletter": "https://www.notion.so/icons/feed_orange.svg",
}

Another thing I wanted to do so the cards would be categorized by a date range was calculate the date of the upcoming Monday and following Sunday to create a string that could be used to categorize them. I think I could do this with a date assigned to the card, but this was the idea I had at the time.

weekday := time.Now().Weekday()
delta := 8 - int(weekday)
start := time.Now().Add(time.Hour * 24 * time.Duration(delta))
end := start.Add(time.Hour * 24 * 6)
daterange := start.Format("01/02") + " - " + end.Format("01/02")

The main bits of the program would loop over the list of cards and create entries in the Notion database that held the cards.

for key, arr := range cards {
	for _, el := range arr {
		title := "[TBD]"
		if el.Name != "" {
			title = el.Name
		}
		_, err := c.CreatePage(context.TODO(), notion.CreatePageParams{
			ParentType: "database",
			ParentID:   dbid,
			Icon: ¬ion.Icon{
				Type: notion.IconTypeExternal,
				External: ¬ion.FileExternal{
					URL: icons[el.Icon],
				},
			},
			DatabasePageProperties: ¬ion.DatabasePageProperties{
				"Name": notion.DatabasePageProperty{
					Title: []notion.RichText{
						{
							Text: ¬ion.Text{
								Content: title,
							},
						},
					},
				},
				"Type": notion.DatabasePageProperty{
					Select: ¬ion.SelectOptions{
						Name: el.Type,
					},
				},
				"Status": notion.DatabasePageProperty{
					Status: ¬ion.SelectOptions{
						Name: "Not started",
					},
				},
				"Day": notion.DatabasePageProperty{
					Select: ¬ion.SelectOptions{
						Name: key,
					},
				},
				"Range": notion.DatabasePageProperty{
					RichText: []notion.RichText{
						{
							Text: ¬ion.Text{
								Content: daterange,
							},
						},
					},
				},
			},
		})
		if err != nil {
			log.Fatal(err)
		}
		time.Sleep(time.Second)
	}
}

Deploying to my scheduler

I have a pretty powerful home server that I use for file storage and test workloads. One of the VMs on my home server runs Ubuntu with Microk8s. I use this with Azure DevOps to deploy a container used to schedule stuff with Go. Using the gocron package I can schedule the above code to run every Friday morning, just in time for me to start planning content for the following week. I’ll eventually schedule these things farther out but its doing me alright for now.

func runScheduler(cmdMap map[string]func()) {
	s := gocron.NewScheduler(time.UTC)

	s.Every(1).Friday().At("08:00").Do(cmdMap["weeklycontent"])

	s.StartBlocking()
}

Here is the makefile I use to build & deploy the container to Kubernetes.

build:
	docker build . -t localhost:32000/go-scheduler

publish: build
	docker push localhost:32000/go-scheduler

deploy: publish
	microk8s kubectl delete --ignore-not-found=true -f ./manifest.yaml
	microk8s kubectl apply -f ./manifest.yaml