Email automation is a critical component of modern marketing. It allows businesses and enterprises to reach out to a large number of potential customers and to update existing customers on new products and company policies.
Using Strapi cron tasks to automate emails is a powerful concept. In this tutorial, we'll build a landing page that allows a company to collect leads in the form of user emails and send automated emails at pre-determined intervals.
The completed version of your application should look like the image below:
The Strapi documentation says that "Strapi is a flexible, open-source Headless CMS that gives developers the freedom to choose their favorite tools and frameworks while also allowing editors to manage and distribute their content easily."
Strapi enables the world's largest companies to accelerate content delivery while building beautiful digital experiences by making the admin panel and API extensible through a plugin system.
To install Strapi, head over to the Strapi docs at Strapi. We’ll be using the SQLite database for this project. To install Strapi, run the following commands:
yarn create strapi-app my-project # using yarn
npx create-strapi-app@latest my-project # using npx
Replace my-project
with the name you wish to call your application directory. Your package manager will create a directory with the specified name and install Strapi.
If you have followed the instructions correctly, you should have Strapi installed on your machine. Run the following commands to start the Strapi development server:
yarn develop # using yarn
npm run develop # using npm
The development server starts the app on http://localhost:1337/admin.
Let’s create our Mailing-list
collection type which will hold subscribers email addresses:
Content-Type Builder
under Plugins
on the side menu.collection types
, click create new collection type
.collection-type
named Mailing-list
.Create the following fields under *product content-type*
:
email
as Email
Next, we create our Email-template
collection type, which will be the model for the Emails we send out:
Content-Type Builder
under Plugins
on the side menu.collection types
, click create new collection type
collection-type
named Email-template
.Create the following fields under product content-type
:
Subject
as Text
Content
as RichText
This will enable us to create the E-mails we send out to our users.
Cron Jobs are used for a variety of purposes. Cron Jobs are used to schedule tasks on the server to run. They're most typically used to automate system management or maintenance. They are, nevertheless, also relevant in the building of web applications. A web application may be required to conduct specific operations on a regular basis in a variety of circumstances.
1 '* * * * * *'
2
3
4* * * * * *
5┬ ┬ ┬ ┬ ┬ ┬
6│ │ │ │ │ |
7│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
8│ │ │ │ └───── month (1 - 12)
9│ │ │ └────────── day of month (1 - 31)
10│ │ └─────────────── hour (0 - 23)
11│ └──────────────────── minute (0 - 59)
12└───────────────────────── second (0 - 59, OPTIONAL)
Follow the steps below to set up a cron job in your Strapi application:
config/server.js
file, then add the following lines of code to the server configuration.1 cron: {
2 enabled: true,
3 tasks: cronTasks,
4 }
cron-task.js
file in the config
folder and add the following1 module.exports = {
2 '01 10 16 * * *': async ({ strapi }) => {
3 console.log('cron running')
4 }
Creating a cron-job in Strapi is as easy as that.
We’ll set up the @strapi/provider-email-nodemailer
.
npm i @strapi/provider-email-nodemailer
config/plugins.js
, add the following:1 module.exports = ({ env }) => ({
2 email: {
3 config: {
4 provider: 'nodemailer',
5 providerOptions: {
6 host: env('SMTP_HOST'),
7 port: env('SMTP_PORT'),
8 auth: {
9 user: env('SMTP_USERNAME'),
10 pass: env('SMTP_PASSWORD'),
11 },
12 pool: true,
13 logger: true,
14 debug: true,
15 maxConnections: 10000
16 },
17
18 settings: {
19 defaultFrom: env('DEFAULT_EMAIL'),
20 defaultReplyTo: env('DEFAULT_EMAIL'),
21 },
22 },
23 },
24 });
.env
file and add your credentials to it:1SMTP_HOST=YOUR_SMTP_HOST
2SMTP_PORT=465
3SMTP_USERNAME=YOUR_SMTP_USERNAME
4SMTP_PASSWORD=YOUR_SMTP_PASSWORD
5DEFAULT_EMAIL=YOUR_EMAIL_ADDRESS
You may use any mail service provider of your choice.
Now that we have our email service, we can update our cron-job.js
file to send emails appropriately.
Open up the cron-task.js
file and edit it’s content with the following lines of code:
1 const marked = require('marked')
2 module.exports = {
3
4 '01 13 15 * * *': async ({ strapi }) => {
5 console.log('cron running')
6 try {
7 let emails = await strapi.service('api::email-template.email-template').find()
8
9 emails = emails.results.reverse()
10
11 const subscribers = await strapi.service('api::mailing-list.mailing-list').find()
12
13 const content = marked.parse(emails[0].Content)
14
15 await Promise.all(subscribers.results.map(async (el, i) => {
16 return await strapi
17 .plugin('email')
18 .service('email')
19 .send({
20 to: el.email,
21 subject: 'Test mail',
22 html: content,
23 });
24 }))
25
26 } catch (error) {
27 console.log(error)
28 }
29 },
30 };
You can set the CRON job for what ever time you want. Furthermore, what our logic states is that we’ll get the most recent e-mail from our email-template
and send that to all users in our mailing-list
.
Obviously, we need a means to collect actual emails for our mailing-list. We’ll build the front-end of our application using Vue 3. Vue
is a JavaScript framework for building user interfaces.
Run the following command to install vue
:
mkdir client
npm init vue@latest
Provide appropriate responses to the prompts, then run the following commands:
cd <your-project-name>
npm install
npm run dev
Your vue
app should be running on the specified port. Open up the app.vue
file and update it with the following code:
1 <script setup>
2 import { RouterLink, RouterView } from 'vue-router'
3 import HelloWorld from '@/components/HelloWorld.vue'
4 </script>
5 <template>
6 <header>
7 <div class="wrapper">
8 <HelloWorld msg="Welcome to UoRos" />
9 <nav>
10 <RouterLink to="/">Home</RouterLink>
11 <RouterLink to="/about">About</RouterLink>
12 </nav>
13 </div>
14 </header>
15 <RouterView />
16 </template>
17 <style>
18 @import '@/assets/base.css';
19 #app {
20 max-width: 1280px;
21 margin: 0 auto;
22 padding: 2rem;
23 font-weight: normal;
24 }
25 header {
26 line-height: 1.5;
27 max-height: 100vh;
28 }
29 .logo {
30 display: block;
31 margin: 0 auto 2rem;
32 }
33 a,
34 .green {
35 text-decoration: none;
36 color: hsla(160, 100%, 37%, 1);
37 transition: 0.4s;
38 }
39 @media (hover: hover) {
40 a:hover {
41 background-color: hsla(160, 100%, 37%, 0.2);
42 }
43 }
44 nav {
45 width: 100%;
46 font-size: 12px;
47 text-align: center;
48 margin-top: 2rem;
49 }
50 nav a.router-link-exact-active {
51 color: var(--color-text);
52 }
53 nav a.router-link-exact-active:hover {
54 background-color: transparent;
55 }
56 nav a {
57 display: inline-block;
58 padding: 0 1rem;
59 border-left: 1px solid var(--color-border);
60 }
61 nav a:first-of-type {
62 border: 0;
63 }
64 @media (min-width: 1024px) {
65 body {
66 display: flex;
67 place-items: center;
68 }
69 #app {
70 display: grid;
71 grid-template-columns: 1fr 1fr;
72 padding: 0 2rem;
73 }
74 header {
75 display: flex;
76 place-items: center;
77 padding-right: calc(var(--section-gap) / 2);
78 }
79 header .wrapper {
80 display: flex;
81 place-items: flex-start;
82 flex-wrap: wrap;
83 }
84 .logo {
85 margin: 0 2rem 0 0;
86 }
87 nav {
88 text-align: left;
89 margin-left: -1rem;
90 font-size: 1rem;
91 padding: 1rem 0;
92 margin-top: 1rem;
93 }
94 }
95 </style>
Open up the HelloWorld.vue
component and update it with the following code:
1 <script setup>
2 defineProps({
3 msg: {
4 type: String,
5 required: true
6 }
7 })
8 </script>
9 <template>
10 <div class="greetings">
11 <h1 class="green">{{ msg }}</h1>
12 <h3>
13 UoRos is a Futuristic company, invested in making sustainabile energy.
14 </h3>
15 <h3>
16 <strong class="bold">
17 Solar + Renewable energy
18 </strong>
19 </h3>
20 </div>
21 </template>
22 <style scoped>
23 h1 {
24 font-weight: 500;
25 font-size: 2.6rem;
26 top: -10px;
27 }
28 h3 {
29 font-size: 1.2rem;
30 }
31 .bold {
32 font-weight: 900;
33 }
34 .greetings h1,
35 .greetings h3 {
36 text-align: center;
37 }
38 @media (min-width: 1024px) {
39 .greetings h1,
40 .greetings h3 {
41 text-align: left;
42 }
43 }
44 </style>
In the TheWelcome.vue
component, place following code:
1 <script setup>
2 import WelcomeItem from './WelcomeItem.vue'
3 import DocumentationIcon from './icons/IconDocumentation.vue'
4 import ToolingIcon from './icons/IconTooling.vue'
5 import EcosystemIcon from './icons/IconEcosystem.vue'
6 import CommunityIcon from './icons/IconCommunity.vue'
7 import SupportIcon from './icons/IconSupport.vue'
8 </script>
9 <template>
10 <WelcomeItem>
11 <template #icon>
12 <DocumentationIcon />
13 </template>
14 <template #heading>Vision</template>
15 Lorem, ipsum dolor sit amet consectetur adipisicing elit. Voluptatibus corrupti eveniet asperiores assumenda, sit quia eligendi porro exercitationem! Vitae ab veniam dolorum voluptates! Iste rerum molestiae nobis tenetur unde odit.
16 </WelcomeItem>
17 <WelcomeItem>
18 <template #icon>
19 <ToolingIcon />
20 </template>
21 <template #heading>Purpose</template>
22 Lorem, ipsum dolor sit amet consectetur adipisicing elit. Alias optio, facere velit officiis dolore doloremque ipsa minima, explicabo, fugiat placeat debitis repellat. Assumenda accusantium enim, aspernatur nemo illo ducimus quia.
23 <br />
24 </WelcomeItem>
25 <WelcomeItem>
26 <template #icon>
27 <EcosystemIcon />
28 </template>
29 <template #heading>Ecosystem</template>
30 Lorem, ipsum dolor sit amet consectetur adipisicing elit. Architecto beatae officia culpa animi quas labore! Ducimus tempora, voluptatibus illo laudantium laborum repudiandae tempore labore fugit at excepturi dolore placeat minus?
31 </WelcomeItem>
32 </template>
Update the About.vue
component with the following code:
1 <template>
2 <div class="about">
3 <div>
4 <h1 class="heading">
5 About UoRos
6 </h1>
7 <p class="desc_text">
8 Lorem ipsum dolor sit amet consectetur adipisicing elit. Esse maiores velit molestias assumenda numquam eligendi. Perferendis pariatur, eligendi at nostrum odio ducimus quod consequatur dignissimos culpa magni commodi, quo esse!
9 </p>
10 <h1 class="heading">
11 What we do
12 </h1>
13 <p class="desc_text">
14 Lorem ipsum dolor sit amet consectetur adipisicing elit. Esse maiores velit molestias assumenda numquam eligendi. Perferendis pariatur, eligendi at nostrum odio ducimus quod consequatur dignissimos culpa magni commodi, quo esse!
15 Lorem ipsum dolor, sit amet consectetur adipisicing elit. Placeat voluptatibus officia atque fuga nesciunt rem iste ab saepe ducimus, praesentium soluta quis temporibus, tempora voluptas facere quos reprehenderit beatae in!
16 </p>
17 <h1 class="heading">
18 Join our mailing list
19 </h1>
20 <p class="desc_text">
21 Lorem ipsum dolor sit amet consectetur adipisicing elit. Delectus eum assumenda dolor laudantium sed temporibus nulla sunt dicta voluptatibus asperiores harum officiis, at, quibusdam beatae corrupti. Suscipit placeat modi corrupti!
22 </p>
23 <div>
24 <form action="" @submit="signUpToMail">
25 <p>{{ error }}</p>
26 <input type="email" class="email_input" name="email" placeholder="Email address" id="" v-model="email">
27 <input type="submit" class="email_submit" value="Sign up">
28 </form>
29 </div>
30 </div>
31 </div>
32 </template>
33 <style>
34 @media (min-width: 1024px) {
35 .about {
36 min-height: 100vh;
37 display: flex;
38 align-items: center;
39 }
40 .heading {
41 /* margin: 10px 0; */
42 font-weight: 700;
43 }
44 .desc_text {
45 margin: 0 0 15px 0;
46 }
47 .email_input {
48 padding: 12px 10px;
49 font-size: 16px;
50 border: none;
51 background: rgb(232, 240, 254)
52 }
53 .email_submit {
54 background-color: hsla(160, 100%, 37%, 1); /* Green */
55 border: none;
56 color: white;
57 padding: 12px 10px;
58 text-align: center;
59 text-decoration: none;
60 display: inline-block;
61 font-size: 16px;
62 margin: 0 5px;
63 }
64 }
65 </style>
66 <script>
67 import axios from 'axios'
68 export default {
69 data() {
70 return {
71 email: '',
72 error: ''
73 }
74 },
75 methods: {
76 async signUpToMail(e) {
77 e.preventDefault()
78 if(!this.email) return this.error = `Enter an email address`
79 const data = {
80 data: {email: this.email}
81 }
82 console.log('Email', this.email)
83 try {
84 await axios(`http://localhost:1337/api/mailing-lists`, {
85 method: 'POST',
86 data
87 })
88
89 } catch (error) {
90 console.log(error)
91 }
92
93 this.email = ''
94 this.error = ''
95 }
96 }
97
98 }
99 </script>
This component is responsible for the API call to our Strapi server.
The old Strapi documentation says this:
Please note that Strapi's built in CRON feature will not work if you plan to use pm2 or node based clustering. You will need to execute these CRON tasks outside of Strapi.
You could for example deploy your Strapi application in multiple containers/clusters/instances, to boost the performance (horizontal scaling). This could for example be in multiple pm2 clusters or Google App engine instances, depending on the hosting.
If you have deployed your Strapi application in multiple instances, then each of those will trigger the cron job and then for example you could have duplicate emails sending because it was triggered & executed in more then one instance.
A solution for this could be to use an third party service that handles the scheduling with for example cron and then uses an custom endpoint to trigger (in one instance) the custom code once.
You could create a custom route/controller that would perform the same logic as a crontask (maybe implement API tokens to secure it). Then you would call this endpoint from any other system.
Could be the built in CRON job system in Linux with a simple curl request or using a more complex self hosted cron system. You can even use things like Zapier or IFTTT, doesn't matter what it is so long as it can make the GET, POST, or PUT request you want (usually GET).
This way the "crontask" endpoint will only be executed on one node of the cluster.
Finally we have our full application, which would enable us collect emails and send them new information about our company regularly at a scheduled time.
Alexander Godwin is a Software Developer and writer that likes to write code and build things. Learning by doing is the best way and it's how Alex helps others learn. Follow him on Twitter (@oviecodes)