Hands-on time. You'll set up a weekly evergreen newsletter that keeps itself on schedule.
Follow along in the video. Pause when you need to catch up in Bento. By the end you'll have a reusable weekly send pattern you can pause for promos later without rebuilding it.
If a step feels unfamiliar, jump back to Defining Segments & Sending/Scheduling Emails or Creating Your First Emails.
Video transcript
I'm going to walk you through how I've set up an evergreen newsletter in Bento that allows for segmentation if I want it, and coincides nicely with how I do my broadcast promos and stuff like that. So the backbone of this, which I'll walk you through in a moment in detail, but the backbone of this is basically that I, in Bento, and in my core document repository, every newsletter that I create [clears throat] has a slug that maps to it that I store in a custom field for that user. And so the way the newsletter loop works is that every week, the cron schedule, which I'll show you in a minute, hits, and it says, "What users are eligible to receive a newsletter?" Finds those users, makes sure that they're not unsubscribed, that they're not receiving a promo, et cetera. And if they're good to go, it checks their current Shadow Newsletters Received field, which is just this comma-separated array of all of the slugs they've received.
It checks that, and it compares it to the environment variable that I've created of all of the newsletters that exist in general. Let me type slug. And it says, "Are there any slugs in this environment variable that the user does not have in their profile?" If so, get the first one they don't have, and then send them the newsletter that corresponds. That's basically how this works.
So before we dig into it, the first question that you might be asking is, why bother with this flow strategy? Why not just do a sequence? And the reason behind it comes down to two things. So number one, and granted, I don't even do this right now, but number one, when I first started this, I wanted to be able to have a segmented newsletter.
So I wanted something where somebody, let's say, so my audience is freelancers. Let's say somebody says their current focus is getting their first client, and somebody else says their current focus is scaling and hiring. These two people need very different content. And so if I send the scaling person all of this get your first client stuff, that's boring for them.
And that if I send the newbie all this scaling stuff, that's going to be overwhelming for them. And so if I were to use RightMessage or something similar to find out where they're at, I could, with my automation flow, segment them accordingly and skip certain newsletters that don't apply to them. So that's one big reason. Another reason is a kind of similar overlap, which is sometimes I will send a broadcast email that maybe it's part of a promotion or a warm-up sequence or something that I want to later add to the newsletter, but not send in the newsletter to people who already got the broadcast.
If it was just a sequence, there's no way to do that. There's no way within a sequence to skip a certain email if a certain condition is true. Whereas when I do it as a broadcast, I can just fire a bulk event for everybody who received the broadcast that adds the newsletter slug to their array, that would correspond to whatever I'm making as the slug for the broadcast when I turn it into a newsletter. Hopefully, that makes sense.
Another reason is day of week. I was just checking, and I don't believe, at least at the time of recording, there's a way that you can constrain a sequence to only send on certain days of the week in Bento. It's all relative to when the user signs up. So if somebody, let's say, signs up on a Monday or a Tuesday, but I usually use Mondays and Tuesdays for sending broadcasts of certain type of content, and I want my newsletter to go out every Thursday, a sequence wouldn't allow that.
Everyone's receiving it at different times. So basically, most of the nerdy stuff I've walked you through, the answer of, "Zach, why do you bother with all this crazy shit?" is just for more granular control and customizability. And in my case, probably the biggest one is that concern about conflicts between newsletters and broadcasts, or newsletters and promos, or the ability to add somebody back to the newsletter if they finish all of them. So with Bento sequences, if you add someone back to a sequence, they would just fast forward to the end if they've received the emails before.
What I've kind of thought, they say no man can stand in the same river twice because the river's different and so is the man. Maybe the same is true about newsletters. And I've kind of thought if I have 50 newsletters or something, why not just start people back at newsletter number one? They're not going to remember what I sent a year ago.
And if I wanted to do that in Bento, I would essentially have to duplicate the sequence, and now maintain two copies of the same content, and now have to remember to update them both whenever I change one, which I know I won't remember to do. So for those reasons, and probably a few more, that's why I do the weird stuff. And now we can walk into how the weird stuff works. So I achieved the functionality with a few different flows that all have specific steps they're responsible for.
At this stage, I could've probably done it all in one flow, but when I originally built it, it worked differently, which I'll show you the old setup. When I originally built it, I relied on some flows killing others so that a user couldn't be in the newsletter twice at once and receive two newsletters in a week. However, the new style that I'm using for initiating, which is using the Bento cron trigger, and cron, if you're not a dev person, cron is just a schedule, basically. It comes from the term cron job, which is a server terminology for scheduled events.
In any case, the new cron scheduler that I use to do this eliminates the risk of somebody receiving it twice, so I have been able to simplify my structure. So I'll give you a high-level overview first, and then we can go into a little bit more detail. So this first flow is the trigger flow, and it looks really complicated, but it's not. So all these events exist because I've had different things that could get somebody to start or stop the newsletter.
So that's all these things at the top, are just different ways somebody could become eligible for the newsletter, and then different ways somebody could become ineligible for the newsletter. So don't be overwhelmed when you see a million things. It's literally just this event starts the newsletter, this event starts the newsletter, this event starts the newsletter, that kind of thing. The important thing is that how it works right now is that we simply add a tag to somebody called Send Shadow Newsletter If Eligible, and this tag is what the cron trigger looks for.
So this one here, the number 10 flow, is the one that's responsible for handling the actual weekly newsletter schedule. So with this one, I just literally added a cron trigger, and every Thursday at 3:00 PM UTC, which is 10:00 AM Eastern, only for subscribers who have that tag will it trigger and then move on to the split. And this split, [clears throat] originally I had as four separate steps, but as you may recall from a different video, each of these takes five minutes to evaluate, and also I was blowing up Jesse's server with a bajillion events. So this chain right here, which I could refactor to a [lip smack] magic split to get it running even faster.
I just had created this before magic split even existed. It's a somewhat new feature. But all this does is it just checks, are they unsubscribed? If yes, don't do anything.
Are they opted out of the newsletter? If yes, don't do anything. So basically, instead of saying, "Are they eligible?" Which would be kind of complex because defining eligibility with chained or conditions doesn't really work. If you say subscription equals subscribed, but that doesn't mean they're eligible.
They have to be subscribed, and they have to meet other conditions. Whereas in this case, if they are marked as unsubscribed, but they are definitely not eligible, if they are opted out of the newsletter, they are definitely not eligible. So in this case, we're matching at true equals bad [chuckles] with an or condition. So if any of these four things are true, then we just have them leave the flow, and they don't get a newsletter.
So unsubscribed is obvious. Newsletter flag is obvious. Basically this one, maybe it's not obvious actually. At the bottom of my newsletter emails, I have a trigger link that they can click to unsubscribe from just the newsletter.
So if they're a customer and they want to still get customer notifications from me, but they don't want to receive my newsletter, that's what this flag indicates. Evergreen newsletter remove, I don't think I'm using that anymore. That was a trigger link kind of thing, I think. It was basically similar to this.
It was just something I did in the past I don't remember anymore. And then lock status is what I use for my [clears throat] promotions, both evergreen promotions, like triggered automatically by a welcome sequence or whatever, or broadcast style promos. In all these cases, we would lock them to that promo, or sometimes I lock them to a lead magnet if I don't want them getting the newsletter at the same time, that kind of thing. So basically, if they're locked to anything at all, that's a flag to not send the newsletter.
And assuming none of those things are true, I trigger this one. So Check Next Issue and Dispatch Send. That's the next flow in the chain. So this one, it looks simple until you look at this part. [chuckles] What this one does, and I have it nicely annotated for myself.
This is the one that checks the environment variable against their own custom field. So within this lesson that you're viewing, I put the liquid code. So this is the unminified, pretty looking one. Here's the minified one that I pasted into Bento.
Basically though, the way that this works is in Liquid, assign is the syntax for creating a variable. So we're creating a new variable for this to evaluate based on the value of the all newsletter slugs environment variable. And so that one is this one. So it's just a list of these slugs separated by the Enter key.
And then in this case, we're splitting, but I think this is not a space. I think it's a new line, or maybe it is a space and a new line is rendered as a space or something. But basically, the thing that you need to understand is that this environment variable, by default in Liquid, is actually one string, like one chunk of text. It's not an array of items.
According to Liquid reading the environment variable, this is not 14 different things. This is one chunk of text, hard stop. But in Liquid, when you split something, what that does is it converts a chunk of text into an array of separate entities, and split is what you're using as the thing to split by. It's what's called a delimiter.
So in this case, we're turning this into 14 different things. So now we know they have these 14 different slugs, and then we do the same based on their received field. That's this one here. Oh, sorry, let me-- Hold on.
Okay. I just had to read my own code before I started rambling over it. So this is a two-step thing. So basically, first step, we get the value of the field, and then I'm defaulting it to being an empty string if they don't have it yet, like if they just subscribed.
And if they do have something in it- Then we split that by a comma, since that's what I used as the delimiter there. So at this stage, we have two arrays, and we just have to compare them, and that's what all of this stuff down here is doing. So it's saying for everything in that environment variable, so it says for every single one of these, one at a time, check line one, then line two, then line three, then line four, et cetera. For every single one of those, we run this unless.
And so unless is the opposite of if. So if this were an if, it would say, "If the shadowNewsletters array contains this one, then do something." But we don't care what the shadowNewsletters array does contain, we care about what it doesn't contain. So this could also be written as if shadowNewslettersReceived array does not contain this slug, then do this. That's what it's doing.
So it says, "Oh, they got this one already. Oh, they also got this one. Oh, but no, they didn't get this one yet." That fires this. So in this case, it says the next slug that the person needs is this one, and then this break tag ends that loop, the for loop, and then we just return that.
So that's what this code snippet did. In the previous section I linked out in the Liquid lesson, let me find what it was called. This one, Liquid in Bento. I linked out to a little GPT I made for helping with Liquid.
It wrote this, and I think I collaborated with it a little bit. But yeah, you can do some cool stuff with that GPT. So back to it. Basically, that big function, what it's doing is it's just setting this temporary field, which I created this field before I developed my newer pattern of the TMP underscore.
So if I were to have rebuilt this today, I would probably call this TMP shadowNewsletterIssueToMerge, but alas, it's too late for that. But basically, this field exists just for a moment and then gets cleared out. It exists only for the following steps to know which newsletter to send them. So at this point, supposing that this Zach guy had not yet received productized services AI leverage, what would happen is that shadowNewsletterIssueToMerge for Zach would equal productized services AI leverage.
So that's the role of this flow here. And then [clears throat] we fire off the next event that triggers the main dispatch with this exact value we just set for them, coming through as an event detail, and that fires off this event. So this one looks, again, kind of complicated and scary, but it's pretty simple, at least compared to what it used to be. I'll show you the old version in a minute.
But basically, every single newsletter in here has the exact same trigger but with a different event detail. So all of them are saying, "If the custom event equals shadowNewsletterSendNewsletterBySlug," which is this, "and the shadowNewsletterSlug event detail equals this specific newsletter slug, then do this event." So this newsletter here, its slug is Easy Way 3X Cold Email Reply Rate. This one is Why Leads Cost 10X Than Should. And so you can see the slug is just here.
And when I write my newsletters, I use Obsidian now as my main place for managing documents instead of Notion, because I can collaborate better with Codex and Claude Code and stuff. [clears throat] When I write a newsletter, Obsidian makes it really easy to keep track of markdown document properties. So here's an example of one newsletter. So you can see we have the shadowNewsletterSlug stored as an actual property on the document. And when I wrote some little scripts and stuff that basically compile the environment variable to copy and paste that...
I'm not going to walk through the details of the script, but basically the script finds any newsletter that's marked as published, takes its slug, puts it here based on its order, because I kept forgetting to paste the new slugs of new newsletters. But yeah, back to it. The trigger is basically just saying, "Did this event get fired with this specific newsletter slug?" And then if so, send the newsletter that corresponds to it. So this is literally just an inline email in that flow.
And then all of these call the exact same event at the end called shadowNewsletterMergeSentEmail, and that's this flow. And it's pretty simple, this flow. I don't think we even pass... Yeah, we didn't pass event parameters.
All we do, I'll find you the little code snippet for this one. Okay, so this is the Liquid. It's pretty simple. So in this case, we essentially have this one here updating a field called shadowNewslettersReceived, and then we're saying if they already have something in that field, so in other words, they've received a newsletter before, then the shadowNewslettersReceived field should equal whatever it already equals, followed by a comma, followed by this new issue that we're sending right now.
That's what this does. So it's just appending the new one. But if they don't have anything in that field, AKA they've never received a newsletter before, then we just set it to this issue. So basically, instead of doing a comma after every sent issue, I'm doing a comma before.
Because if there's a comma after every single one, I think it could create some weird Liquid issues when parsing an array because you'd have an item and then a comma and then nothing. And when Liquid does the split by comma, I don't know what would happen, but I suspect it would produce one empty array item, which would probably mess stuff up. But I haven't tried it. Maybe it would be just fine.
I don't know. But this is how I chose to solve the problem. And so if you can believe it, that's all the steps. Simple as that.
So to recap, basically step one, we do something that initiates them getting added to the newsletter, AKA joining the email list. Pretty much anything somebody does, like any lead magnet they sign up for or whatever, we add this tag that says they are generally eligible as long as they pass the other criteria. So that's why I say send it if eligible. And then every week at the scheduled time, so 10:00 AM Eastern on Thursdays, which by the way, I'll probably shift to a two times a week newsletter soon.
And when I do that, I will have to have-- I think you can only have one cron trigger per flow. Let's see. Yeah. So down the line when I switch to two a week, I'll essentially have two flows that are basically the same, but they just have a different trigger.
Or maybe instead of doing this split in that flow, they'll both call some other centralized one so that I can have the old developer do not repeat yourself thing be true for my Bento. Let me just make sure I didn't save it with the second cron. So yeah, so that's the next step. So every Thursday at 3:00, we make sure the people with the tag are eligible, and if so, we route them in.
And then when that happens, we look at their environment variable compared to their custom field to see which newsletter slug they should get next. And then we route them into the newsletter with that slug. And then we make sure that the newsletter we routed them into is updated on their field so that we don't send them the same one in the future. The biggest weak point if you build something like this for yourself is the slug.
I had a staff member in the past who would build these before I streamlined it and did it myself, and sometimes he would not paste the slug into the correct places. And what would happen is we would have it keep saying, "Oh, this person doesn't have the slug because the slug that was pasted is slightly different, or it's one that was there for a previous email or something like that." And then the user would just keep getting the same email again and again and again and again, because it's only checking, do they have slug A, but actually they would never get slug A. I don't know if that makes sense to you. So what I did to make this simpler is I just marked these little to-dos for myself.
So like every week or whatever, when I go to make a new one, I have an SOP that I follow to just remind myself all the steps I'm supposed to do. So I build out this little shell, and it's like, I've got to add the slug there, and I have this little card that says, "Put the slug here, build and test the email, connect it when done," et cetera. So I would suggest you do something like that. And then finally, if you're going to go the Obsidian route, working with markdown files or whatever, if you wanted to see all the properties I use, here they are.
So I've got Shadow Newsletter Slug, Created At, Newsletter Type. Can I make these wider in Obsidian? No, I don't think I can. Newsletter In Use, Newsletter Is Published.
So the difference between these two is that I revisited how I write earlier in the year and killed a lot of my newsletters. So I went from, I don't know, 40 or something down to 10. I'm starting over. And so that's the differentiation here is did I actually hook it up and publish it versus is it done?
That kind of thing. So you don't have to do any of these if you do it. This is just how I'm personally keeping track of different statuses. I have the subject and preview here because I have some script that auto builds my Bento email for me based on the markdown.
We won't get into that here. And then these other things like the word count, one-liner, the score, and the order. These three are AI scoring things, so you don't really have to worry about that. And the order is used for me actually ordering it here in the database display in my Obsidian and in the Newsletter Slugs Master file.
So basically it'll take all the published ones when I run the compilation script, and it will put them in this file in the order that they are published. But all of these steps could be manual. I just have to script these things because my ADHD brain will forget. So hopefully you found this helpful, and see you in the next video.