# Mailing List
Make a mailing list subscription page with Node Express. Deployed on Heroku.
Check final product here! (opens new window)
- Server with Node Express.
- Mailchimp API to send mailing information.
- Deployed on Heroku.
- Configuring subdomain
# Server with Node Express
# #Express #Node
# Flow
npm init
npm i express body-parser request
Build basic setup
const express = require("express"); const bodyParser = require("body-parser"); const request = require("request"); const path = require("path"); const app = express(); const port = 3000; app.use(express.static("public")); app.listen(port, () => { console.log("listening"); }); app.get("/", (req, res) => { res.sendFile(path.join(__dirname, "/signup.html")); });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- Static Files
CSS and image file is gone when we check on localhost:3000. This is because those files were local. We need to change them to static files in order to send them.
//index.js
app.use(express.static("public"));
2
<link href="css/signup.css" rel="stylesheet" />
Change the url to relative url starting from 'Public' folder.
background-image: url("../image/bg.jpg");
In case of CSS, starting from the CSS file to destination
@font-face {
font-family: "Intro";
src: url("fonts/normal_normal_normal.woff2") format("woff2");
}
@font-face {
font-family: "Intro";
src: url("fonts/normal_normal_bold.woff2") format("woff2");
font-weight: bold;
}
2
3
4
5
6
7
8
9
10
font doesn't seem to mind to begin from public folder. Or maybe its cache. Doesnt seem to affect font although I deliberately changed the URL to nonsense. Must check later.
POST and Parse
app.use(bodyParser.urlencoded({ extended: true })); // if you don't use bodyParser `req.body` returns undefined. app.post("/", (req, res) => { console.log(req.body); const firstName = req.body.firstName; const lastName = req.body.lastName; const emailAddress = req.body.emailAddress; });
1
2
3
4
5
6
7
8
9
# Notes
# #static files #bodyParser
- Static files
app.use(bodyParser.urlencoded({extended:true}))
What is bodyParser? urlencoded? and extended? Why do we need to install it if it is so fundamental?
# MailChimp API
- You can control your mail list for marketing purpose.
- Lists/Audience means a group. Member/contact means a person.
- You can create one list if you're a free tier user, and add members to it.
- They have their own package to make things easy
npm install @mailchimp/mailchimp_marketing
.
# Flow
Use the boilerplate code to check if its ok, but first you need to set
"type":"module"
at package.json to import stuffs.import mailchimp from "@mailchimp/mailchimp_marketing"; // this part does not work on normal package. mailchimp.setConfig({ apiKey: "longnumbersofAPIkey", server: "us00", }); async function run() { const response = await mailchimp.ping.get(); console.log(response); } run();
1
2
3
4
5
6
7
8
9
10
11
12
13
14After testing, you can add to the mailing list with
const mailchimp = require("@mailchimp/mailchimp_marketing"); const listId = "You can find it at MailChimp"; mailchimp.setConfig({ apiKey: "api", server: "us00", }); app.post("/", (req, res) => { console.log(req.body); const firstName = req.body.firstName; const lastName = req.body.lastName; const emailAddress = req.body.emailAddress; const subscribingUser = { firstName: firstName, lastName: lastName, email: emailAddress, }; async function run() { try { const response = await mailchimp.lists.addListMember(listId, { email_address: subscribingUser.email, status: "subscribed", merge_fields: { FNAME: subscribingUser.firstName, LNAME: subscribingUser.lastName, }, }); console.log( `Successfully added contact as an audience member. The contact's id is ${response.id}.` ); res.sendFile(__dirname + "/success.html"); } catch (error) { console.log(error); res.sendFile(__dirname + "/failure.html"); } } run();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# Notes
# #POP #IMAP #SMTP #async #await #asynchronous #promise #callback
What is POP and IMAP SMTP?
AsyncFunction
Newer way to implement promise and callback functions.
function resolveAfter2Seconds() { return new Promise((resolve) => { setTimeout(() => { resolve("resolved"); }, 2000); }); } async function asyncCall() { console.log("calling"); const result = await resolveAfter2Seconds(); console.log(result); // expected output: "resolved" } asyncCall(); //Using await inside aync function allows you to write cleaner style promises
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//How to use then/catch -> try/catch async function logTodoTitle() { try { var user = await fetchUser(); if (user.id === 1) { var todo = await fetchTodo(); console.log(todo.title); } } catch (error) { console.log(error); } }
1
2
3
4
5
6
7
8
9
10
11
12
13Promise
“A promise is an object that may produce a single value some time in the future” . An object used for asynchronous .
Asynchronous / Callback
# Deploy on Heroku
Cloud platform as a service (Paas) started in 2007, initially only supported Ruby. Now supports Java, Node.js, Scala, Clojure, Python, PHP and Go. Acquired by Salesforce in 2012. The name "Heroku" is a portmanteau of "heroic" and "haiku". The Japanese theme is a nod to Matz for creating Ruby. Wikipedia (opens new window)
# Flow
First install heroku cli
npm i -g heroku
Change port
const port = process.env.PORT; app.listen(port || 3000, () => { console.log("listening"); }); //The || lets you allow locally and deployed
1
2
3
4
5
6
Create procfile
//Procfile web: node index.js
1
2This step might not be necessary anymore.
You can either push directly to heroku or use github to deploy. I'll try using github.
Make repository and push to github. Go to heroku app and integrate with repository, deployment is easy but
# .env files and API_KEYS
# #.env
As soon as I pushed, my API_KEYS which were included in the index.js file was compromised and disabled. I should never do this again. I had to find a way around.
npm i dotenv
require("dotenv").config();
mailchimp.setConfig({
apiKey: process.env.API_KEY,
server: "us18",
});
2
3
4
5
6
// .env file
API_KEY = "98c71dummydatacc43bb3164354e-us18";
2
but how to use it deployed?
First try
Error: Unauthorized
Second try
Finally works!!!
Sent my first newsletter to my friends!
# Configuring my subdomain to use on Heroku appp
# #subdomain #cname
You can make subdomains and give to independent websites. ex) blog.keithkwon.dev, newsletter.keithkwon.dev ...I configured so that
newsletter.keithkwon.dev
will point to my new heroku app
stackoverflow (opens new window)
# Flow
Upgrade to paid user in Heroku, necessary for auto SSL.
Allow subdomain by Heroku console.
heroku domains:add cool.tutorial.com
1May not be necessary.
Register for auto SSL on Heroku and add custom domain
Configure CNAME on google domain
# Notes
- What is Redis? What is PostgreSQL?
- What does process.env have?
- What is a websocket?
- How to use .env files
- What is github actions?
- About Hosting : ANAME CNAME DNS SSL Synthetic Records, Custom Resource Records, DNSSEC, NAME SERVERS