Tuesday, 14 January 2025

App Review: Haunted Dorm

Think about all the tropes you've ever seen in Asian horror and even movies like the A Nightmare On Elm Street series and It. Think of all the times the characters triumphed over evil by virtue of their imaginations and teamwork.

And then download this game - Haunted Dorm, by China game company Mihuan, for a fun time.


This is a Tower Defense game with a single goal - to defeat the monster lurking the hallways before it comes for you. To that emd, you'll have all the classic tools for a Tower Defense; namely, missile weapons and resource generating items. The difference is that there is one main enemy to defeat. Sometimes there are multiple enemies, but only the demise of that one monster will win the game.

The Premise

The dorm is being haunted by a monster. You play as one kid in a group of children, going to a room and sleeping to gain enough resources. These resources will build your defences to hold off the monster's attacks, and eventually destroy it.


Once the monster breaks through the door of the room you choose, you're as good as dead and the game ends.

The Aesthetics

Haunted Dorm is rendered in what one would call "kiddy graphics". Everything looks hand-drawn and crude, like toddlers made this. It's a bold stylistic choice, though sometimes comes off as clunky.

The Experience

It's definitely a spooky experience, especially the first few days you play it. The atmosphere is haunting. The graphics are kiddy and this could end up either being comical or adding to the spookiness.


But there's also that sense of solidarity as you, the player, team up with other computer-controlled players to take down the monster.

The Interface

Drag your finger over the screen to move the avatar. Tap on buttons to perform actions. Select items to be built from menus.


It's not exactly complicated, though the execution feels clumsy.

There are also some items, like the machine gun, that you will need to tap and hold, and drag, in order to use. It's awesome, but not terribly intuitive.

What I liked

The music. It adds so much to the atmosphere, I can't rave enough about it.

Dream Hunter Mode. You get to play as the monster instead of the kids... and lemme tell you that this feature is fucking insane. Whereas in Normal Mode you had the aid of computer-controlled allies, in this mode it's the opposite - all these are working against you now. Still, what a feature!

Seven Monsters Mode. More interesting levels where you can't purchase Special Objects. This was pretty challenging.


Story Map Mode. Different rooms with different rules of engagement.

Ghost And Angels Mode. This is another mode where any allies who die, become a ghost as well. Good, chaotic fun!


The comments made when you steal stuff from other players, are amusing. Especially the awkward English translations of what was obviously Chinese text to begin with.


This solar beam is awesome beyond belief. Not only does it pinpoint the Monster's location, it deals damage and earns coins! And you can even upgrade it for bonus damage. Talk about overkill.


The entire game was ridiculously easy to play. Even with comically bad English translation, it was a breeze to just tap my way around and try shit out.



What I didn't

Unless you have the Trampoline, there really does not seem to be any way to escape what I call the Ghost Hole attack, or just spam-filling every square inch of the room, which can be super-tedious if you chose a large room.


The menu tends to go out of the screen sometimes depending on where you tapped the button. It's a layout issue and annoying AF. Do better!

Amy Can't Die Mode seemed like a waste of time. You basically sleep in one room with a bunch of other kids and you use your combined powers to hold of wave after wave of hostiles until you finally assemble a weapon strong enough to kill the Boss. It was an interesting concept, but the execution was so weak.



When there are objects appearing around the corridor, it's always a crapshoot as to what you'll end up grabbing before the others do. There's just so little time to react. At least give players and option to pick something else other than what they initially picked up!


I don't understand this game's obsession with frogs and toads. At all. It can be a little off-putting.




Conclusion

Haunted Dorm is simultaneously campy and goofy, and deliciously creepy. I found Normal Mode quite addictive and all the other features combined should provide great replayability. This is a game that doesn't take itself at all seriously, always a plus. One definitely could do worse than give Haunted Dorm a shot.

My Rating

6.5 / 10

Not spooktacular, but damn solid!
T___T

Thursday, 9 January 2025

Spot The Bug: Class Action!

Time for more Spot The Bug, fellow geeks! Today I'm going to detail a bug I introduced while working on a shopping cart application that was written by someone else a while back.

Prepare to be
squashed, bugs!

Since the code base is pretty large, I'm not going to paste everything here, just enough that you get the idea. So this was the product page where the Add To Cart button was. And this was perfectly OK.
But if this was a "basic" product, I had to insert a button that would take me to the "premium" product. So I wrote some jQuery code that inserted a button which would direct the user to the "premium" version. I used the same class, atsc_button, as that of the Add To Cart button so that the styling would be consistent.
if ($(location).attr('href').indexOf('basic-') != -1) {
    var button = $("<button>PREMIUM</button>");
    var newUrl = $(location).attr('href').replace('basic-', 'premium-');
    $('.desc').append(button);
    button.addClass('atsc_button');
    button.click(()=>{$(location).attr('href', newUrl); });
}

This was the result, visually.

What Went Wrong

Unfortunately, when I clicked on this new button, now it would attempt to add the current product to the cart! And then go to the "Premium" URL.

I knew this because of the error alert. Apparently the application was trying to send a null product.

Why It Went Wrong

Upon further investigation, I found this piece of code in the code base, linking the atsc_button class to a function that seemed to be adding the product to the cart.
$('.atsc_button').click((e)=> { AddToCart(e.currentTarget.dataset['id']); });

function AddToCart(id) {
    if (!id) alert("Error; product not found.");

The id of the product was determined by reading the data-id attribute of the button that had been clicked. Since the new button didn't have such an id, it triggered an error.

How I Fixed It

I went to the CSS and found the styling, then replicated it for the gtp_button class....
.atsc_button, .gtp_button {
    width: 15em;
    height: 4em;
    padding: 1.5em;
    border-radius: 5px;
    font-weight: bold;
    background-color: rgb(155, 155, 155);
    color: rgb(255, 0, 0); border: 0px;
}

And changed the class name of the button I added.
if ($(location).attr('href').indexOf('basic-') != -1) {
    var button = $("<button>PREMIUM</button>");
    var newUrl = $(location).attr('href').replace('basic-', 'premium-');
    $('.desc').append(button);
    button.addClass('gtp_button');
    button.click(()=>{$(location).attr('href', newUrl); });
}

After these changes, the button redirected to the "Premium" page as intended and there was no further drama.

Also, apparently "atsc" stood for "Add To Shopping Cart" (go figure, huh?) and now "gtp" stands for "Go To Premium". 

Moral of the Story

One gotcha of jQuery, indeed, JavaScript as a whole, is that while CSS classes should be used for styling, often they can also be used for functionality. In this case, it was both.

Possibly a better way would have been to separate the styling from the functionality, so that the look and feel could then have been reused. My way of fixing the problem wasn't great either, for obvious reasons, though it served just fine and hopefully I never have to go back to this God-awful code again.

Orange you glad we fixed this?
T___T

Friday, 3 January 2025

Five Overrated Virtues in the Workplace

Nine years ago, I wrote about the three virtues of a programmer. Having spent some number of years making an honest living as a software developer, I've come across quite a number of virtues that don't make the grade. They are virtues, but only in appropriate doses. In a work setting, focusing on them for their own sake, can lead to ruin.

Be virtuous.

They say you should put yourself into your work. That's a load of baseless claptrap. At work, it pays to not apply many virtues you would to your own life. Because your office is not your home, your colleagues are not your friends, and your employer is not your dad. Unless of course, your office is literally your home, your colleagues literally your only friends and your dad literally is your boss, in which case I can offer you only my sincerest condolences... and an exhortation to make better life choices.

But enough of that! We're here to explore some virtues in the workplace that are not only outdated, but dangerously overrated.

1. Hard Work

Few employers are going to tell you that hard work is a bad thing. That's because hard work isn't a bad thing. In the absence of actual talent, hard work can be your only saving grace. Therein lies the problem; focusing too much or solely on the ability to work hard makes it look like it's your only saving grace.

It's not that being willing to work hard isn't important. It's just that being effective at your job enough that you don't have to work hard, is of significantly greater value. After all, horses and oxen can work hard, too. What separates a hard worker like yourself, from those simple creatures?

Oxen work hard too, so what?

Forget what the older generations tell you about hard work. They were fortunate to exist in a time where actual talent wasn't that big an issue and hard work could provide an honest living. Times are different now. We have automation and robots, and soon we will have A.I. No amount of "hard work" is going to overcome the advantage that machines have over human beings.

Ultimately, work is about results. As long as acceptable results are produced, it should not matter if hard work wasn't put in. In fact, the less work the better. Every innovation in the past century has been about reducing work and producing more. 

Unless you enjoy being laughed at, stop talking about hard work to programmers. Hard work is antithetical to the quintessential programmer. Our job, at its core, is to eliminate hard work by automating it.

2. Honesty

Honesty involves telling the truth. And telling the truth is commonly known as a virtue... until it starts to do harm. Remember when Jack Nicholson barked "You can't handle the truth!" to Tom Cruise's character in the thrilling climax to A Few Good Men? He might as well have been saying that to us, the audience. Not everyone can handle the whole unfiltered truth; in fact, most of us can't. Most of us look at facts through certain lenses or perspectives, in order to cope.

The late great Adrian Tan had this to say during a NTU Convocation speech in 2008
Any child can blurt out the truth, without thought to the consequences. It takes great maturity to appreciate the value of silence.


So stop treating honesty like a child would, like it's the be-all-end-all of virtues. Even the truth has context. People who insist on honesty, funnily enough, are often incapable of handling it.

While telling the truth is often a good thing, the truth is also packaged in a way to be useful. What use is the whole unvarnished truth if listeners get confused, offended or otherwise disinclined to listen, and simply discard the entire message? It is not enough to tell the truth. When, where, and how to tell it are equally important, if not more so.

The value of silence.

In effect, it's a matter of knowing when to speak, and what to say. And knowing when to STFU. Many people forget that keeping their damn mouths shut is often also an option.

But yes, to own thineself be true. One should always endeavor to be honest to oneself. To everyone else... exercise discretion.

People like to tell me that they find my frankness refreshing. That's hilarious. Do they even have any idea how much I hold back on a daily basis? If I went for full unfettered honesty every damn time, if I said exactly what I thought every single time, I would have burned every bridge available by now. If people had even an inkling of how little I think of them, they wouldn't find my frankness quite so appealing.

Honesty is one of the most overrated virtues in human history.

3. Loyalty

This next virtue is one that you'll often see employers harp on. Loyalty - the ability to stick with a company no matter how much better conditions are elsewhere. And let's get one thing crystal clear - things can always be better elsewhere and most employers worth their salt aren't unduly worried. It's only when things are often significantly better elsewhere, that's when you hear employers yammer on repeatedly about loyalty.

I say loyalty is overrated. I mean, seriously? Dogs are loyal too. If all you have to contribute is loyalty, what separates you from Man's Best Friend?

You want loyalty?
Get a dog.

While loyalty is overrated, that's not to say it's a bad thing. However, employers have a concerning habit of mistaking a lack of options, for loyalty. Back in the day, employees put their careers at the mercy of their employers and pledged their lifelong loyalty. And in many cases, they were even rewarded in the form of promotions and pensions.

Those times are gone, and they're not coming back. In a world increasingly driven by capability and competence, promoting your longest-serving employee on the basis of duration of service alone, is laughable. It's insane. And when having to restructure in order to save on costs, it would not be sensible practice to retain the longest-serving (and sometimes, also the highest paid) employees purely on sentiment. This goes against business principles. Thus, while employers still say they value loyalty, they're no longer able (or willing) to pay for it and employees should adjust accordingly.

4. Being Right

This next overrated virtue is tangentially related to honesty. And that is, always needing to be right. Not being wrong. Even for a second.

Only machines can't be wrong. If there's ever an instance where they do not produce perfect output, that's because they weren't programmed to do so, or the user did things wrongly. By definition, it's not the machines being wrong. You never, ever, want to be thought of as a machine, or to be held to a machine's standards. Because you're not a machine. That would be akin to a rabbit being held to the standards of, say, a hippo.

I may be biased, but hippos
are way more awesome.

In essence, don't get hung up about being right, or take it personally when your assertions are challenged. Maybe it's just me, but I feel like academics suffer from this an awful lot. Being wrong isn't that big a deal when the lives of millions of people don't hang in the balance over the correctness of your work. And let's face it; in most cases, it doesn't.

Being right all the time should not be the goal. Being right should be an objective to work towards, but the journey to get there should be valued more because that's where you learn the most. You don't learn anything from being right all the time; in fact, it promotes complacency and worse, it introduces fear of change. You don't ever want to try something new because it could be (gasp!) a mistake. You know how many mistakes I've made from the day I wrote my first Hello World program? Countless. Some were minor. Some were embarrassing. And some got me in serious trouble. And by the time this blogpost is published, I'll have made countless more.

Not making mistakes is great. What's even better is the ability to recover and learn from mistakes.

5. Ambition

It's admirable to be driven by a desire to do great things. Ambition has driven many amazing feats. It's given rise to innovations that will echo on through eternity. Steve Jobs is long dead, but he lives on through the iPhone.

Steve Jobs was undoubtedly a visionary, but let's be real. The creation of the iPhone is owed not only to his genius, but to the sweat and tears of thousands of anonymous workers who have their labor to the cause. Without that labor, Jobs could have taken his ideas and basically gone off to fuck himself. What are the chances of you being a Steve Jobs, rather than one of the countless anonymous workers?

The pyramids, similarly, weren't built by a handful of men with great vision and ambition, but by thousands of slaves whose only ambition was to not starve. What are the chances of you being the guy with the whip, rather than one of the many dudes hauling those stones?

Was it only ambition that
built things like these?

The problem is that these days, everyone seems to think that they're destined for some great purpose. They think about their legacy, and want their deeds to leave an echo long after their deaths. Legacy? That's just your vanity talking, buddy. Statistically speaking, you're far more likely to be a schmuck like the rest of us. And having ambition means you will never be OK with that. Which would be fine if your capabilities actually matched your ambition.

Earlier, I talked about speaking the truth. Here's an ugly truth: many think they're special and talented and that the rules don't, or shouldn't, apply to them. They are largely (and tragically) mistaken. Everyone's unique in some way, but not enough to move the needle in that regard.

The problem with wanting to be remembered long after your death, is that not everyone can be Albert Einstein and make great scientific inventions. Not everyone can perform feats of supreme athleticism like Usain Bolt. The vast majority of us are painfully average. That's how averages work. (More medians if we wanted to be pedantic about it)

But the Internet and Social Media somehow make people feel like they should be able to make themselves memorable. More often than not, they end up going through life being bitter and feeling like failures... because they couldn't reach those largely imaginary lofty heights.

All things in moderation!

There's no doubt that the five things I listed above are virtues. However, they're overrated. Some way overrated. It can't hurt to practice some (or even all) of these, but make them a selling point of your professionalism at your own peril.

Remember, programmers. Evolution is a constant in our industry. This does not just apply to tech; it applies to the virtues you display in the workplace.

Stay virtuous,
T___T

Sunday, 29 December 2024

Not My Job, Not My Problem

"Not my job." These three words are often considered bad form when uttered by an employee; but are they, really? As always, context is king.

Employers seem to think that within the confines of their company, their word is law, and therefore anything they assign to you is automatically your job regardless of how far it deviates from the actual job description. In many cases, there's even literally a clause that says (to that effect) that the employee's duties include anything else not listed in the description that their supervisor may choose to assign to them.

Nobody respects a doormat.

It can't be said enough, that being seen as the office doormat is the mother of all bad ideas. In both the big and small picture, there is absolutely nothing in it for you.

On the other hand, the stereotypical Singaporean has this famous attitude of not wanting to lose even in petty ways. This attribute of missing the forest for the trees is exactly what stops people from becoming all they could be. Singaporean employers want to milk as much value out of their employees as possible, and Singaporean employees don't want to give away extra work for free. How does one resolve such a contentious relationship?

The case for taking on extra duties

Occasionally venturing outside the confines of your job scope can be a good thing. You pick up valuable experience and you earn a reputation as a team player. If you save your employer the trouble of hiring someone to do a job that you can take care of in a jiffy (and doesn't interfere too much with your actual job), that's automatic justification for your continued employment.

How did I initially get experience in DevOps? By volunteering for the task when a colleague left the company. I could have said I had no bandwidth for extra tasks and left it at that, but it was an opportunity to pick up new stuff. Plus, I was pretty sure at some point they were going to finger me for the role anyway, so I might as well get it over with, amirite?

Also, if you do this enough, people generally tend to cut you some slack for minor infractions. Show up thirty minutes late three days in a row? Miss a deadline? Take too many smoke breaks? Hey, what can people reasonably expect when they continually pile new things on your plate? What are they gonna do; fire you and coax some other poor sucker into taking over your workload? I'm not saying that won't happen; I'm saying that people are generally smarter than that.

Too many smoke breaks?

Automatically saying "not my job" regardless of context, even if you're technically right, makes you look like a pain in the ass to work with. Now, if you're extremely good at your job and not readily replaceable, you can reasonably expect to get away with it. Otherwise, drop the attitude or find new employment.

Do I sound like a corporate shill yet? Be patient; I'm about to get to the cases against doing extra work.

The case against

If you're too agreeable and constantly take on new unrelated tasks, people are going to see you as their go-to option for dumping stuff on. They no longer see that thing they asked you to do, as a favor. It has become your job. Now if you were being paid for that extra workload, that would be a different story. But chances are, you're not. That extra work doesn't get factored into your performance appraisals when it comes to determining raises or promotions. And if enough jackasses are in Management (sometimes just one is enough!) that extra work may not even count as a mitigating factor for the occasional infraction.

It's also counterintuitive to do someone else's work for them. This robs them of the opportunity to do the work themselves, and possibly learn from it. Also, you don't get paid for it, and your actual work suffers.

There were times when I received data that needed to be processed by the system. And during those times, I noticed some typos. Spelling and grammatical in the data that would still make them processable without compromising data integrity, but could potentially be embarrassing if the customer spotted them.

Out of habit, I sometimes quietly corrected those errors, and moved on. That was in my first year on the job. By the third year, I had come to an entirely different conclusion. I work in software development, not marketing. In terms of pay grade or job description, correcting elementary grammatical errors is not my job at all. If people can't be bothered to run their work through a spell-check, well, they're not my kids and I'm certainly not their babysitter. Also, by that time, the number of things on my plate had grown to such an alarming extent that I doubt my boss would have approved if he'd suspected I spent even a few minutes a day correcting typos. They didn't need me to do that job. They needed to hire better people.

I've said before that taking on new stuff might be a good way to level up and get out of your comfort zone. But what if the task at hand is not something you can afford to screw up? Like flying a plane, or disarming a time bomb. Basically something that you aren't qualified or experienced enough to do, and has serious negative consequences for failure.

One idiot boss, one
broken table.

I once worked at a company where a colleague was asked to move a table from one room to another. It wasn't in his job scope, and he did his best, but somehow the flimsy table collapsed. Partly it was due to mishandling, but to be fair, he just wasn't trained to do this stuff.

Our boss lamented to me that this guy had been underperforming and he was giving him a chance to be useful, but he had screwed even that up. This is an unfortunate tendency of some employers - no matter how unreasonable or illogical the expectation, they somehow think they're doing you a favor by holding you to it, or giving you some grand opportunity. Honestly, if the employee had injured himself in the process of carrying out that task, I'm pretty sure a lawsuit would be both tenable and well-deserved.

Rule of thumb: will those tasks interfere with your actual job? Then the default response is "no", unless you have documentation in black-and-white. To that end, if someone starts asking for favors too often, insist that they formally send you that request in an email that's CC-ed to your boss, to HR, to whomever else you deem appropriate. This accomplishes two objectives. Firstly, the appropriate people have visibility over what you're being asked to do or at least have no plausible deniability. Second, people are usually asking you to do shit for the sake of their convenience. If it stops being that convenient, they'll stop doing it. If their requests draw negative attention from people further up the pecking order, they'll even actively avoid doing it.

People in the workplace are generally driven by self-interest. No one is working for any significant reason beyond earning a paycheck. Learn to exploit that, and you should be fine.

The most important point, at least in my view

You've heard all the arguments I have against doing someone else's job for them. But these are the biggest ones, and I've saved them for last. Here they are: professional responsibility, and organizational process.

In any organization, there are systems and processes in place for a reason. Certain people are put in positions of responsibility, also for good reason.

Surely having more people work on a task can only be a good thing? More is not always merrier, and I'm about to explain why, using a very simplistic example.

Say for instance, I'm in charge of a database management system. Any changes, any data that goes in or out of it, is my responsibility. I have to know. And then my colleague, who has certain permissions in the system, spots what he thinks is a mistake in the configuration and decides to rectify it on the spot. But since he's doing what he sees as extra work (which is always good, right?) therefore subconsciously he neglects due process, which is to let me know when doing stuff like that.

The configuration results in some behavioral changes in other systems that connect to this database. It's a while before things get traced back here, to the database management system which I'm in charge of. To my horror, I don't even remember making the changes that resulted in the erratic system behavior. Why's that? Because I didn't make those changes. But here's the kicker - I'm responsible for them anyway.

My hypothetical colleague, thinking he was being helpful and thinking his extra effort would help the company, neglected due process and cowboy-copped his way through the system configuration. And because the database management system wasn't his professional responsibility, he didn't see the need to follow up. With the very best of intentions, he screwed the pooch because he introduced configuration changes into a system that he wasn't in charge of, and failed to notify those who were in charge of it.

Now I know what most people would be thinking at this point - wouldn't there be safeguards in place to prevent this sort of thing from happening? Yes, that would be correct, which is why, thankfully, the example I just gave you is only hypothetical. But the principle remains the same. You can't attempt to "help", thinking that it's all good because you're doing more than you're paid to do, and interfering with people who are actually in charge of the job you're trying to help with. People who have to be responsible for the results. Not you.

You're not running
a lemonade stand.

Organizational process may not be very important if you're, say, running a lemonade stand. Unfortunately, for the vast majority of workplaces, the systems are far more delicate, the consequences far more dire, than that of a lemonade stand.

So yeah. Professional responsibility and organizational process. Before trying to do extra, make sure you're accountable to the people who actually matter. If your unsolicited helpfulness results in man days lost for your co-workers, I doubt they'll be feeling very grateful.

Final words

There's always a little give-and-take in a relationship, especially a professional one. By all means, do extra. But never be shy about letting your colleagues know that they're treading a fine and dangerous line. In other words, be obliging, but don't let your goodwill be taken for granted.

And remember, doing more work is not always better - for you or your employer. 


Your problematic programmer,
T___T

Monday, 23 December 2024

Web Tutorial: NodeJS Christmas Mailer (Part 3/3)

Time to do some mailing! We'll need to install the nodemailer package like so.
install --save nodemailer


This package handles transport for SMTP. We'll only require nodemailer during this route.

app.js
app.post("/process", (req, res)=> {
  var nodemailer = require("nodemailer");
  
  console.log(req.body);
  res.redirect(303, "/thankyou");
});


Once we've assigned the resultant object to the variable nodemailer, we call the createTransport() method and assign the result to transport.

app.js
app.post("/process", (req, res)=> {
  var nodemailer = require("nodemailer");

  var transport = nodemailer.createTransport();

  console.log(req.body);
  res.redirect(303, "/thankyou");
});


When you run createTransport(), you'll need to pass in a JSON object with the following properties. Inside it, auth is an object as well with user and pass as properties.

app.js
app.post("/process", (req, res)=> {
  var nodemailer = require("nodemailer");

  var transport = nodemailer.createTransport({
    service: ,
    host: ,
    secure: ,
    port: ,
    auth: {
      user: ,
      pass:
    }
  }
);

  console.log(req.body);
  res.redirect(303, "/thankyou");
});


I'll be using Google's Gmail for this. If you'r also using Gmail, the service, host and port properties are as follows. You need to look up the values for any other service you'd like to use. secure will be set to true in most (if not all) cases.

app.js
app.post("/process", (req, res)=> {
  var nodemailer = require("nodemailer");

  var transport = nodemailer.createTransport({
    service: "Gmail",
    host: "smtp.gmail.com",
    secure: true,
    port: 465,
    auth: {
      user: ,
      pass:
    }
  });

  console.log(req.body);
  res.redirect(303, "/thankyou");
});


For auth, we'll use these values. Remember how we set the secret in the auth.js file? Well, we'll be doing something similar here.

app.js
app.post("/process", (req, res)=> {
  var nodemailer = require("nodemailer");

  var transport = nodemailer.createTransport({
    service: "Gmail",
    host: "smtp.gmail.com",
    secure: true,
    port: 465,
    auth: {
      user: auth.mail.user,
      pass: auth.mail.password
    }
  });

  console.log(req.body);
  res.redirect(303, "/thankyou");
});


In here, I have the mail object with user and password as properties. user is the email address I want to use, and password is the app password I obtained for this. Here's the procedure you need to follow for a Gmail account. The email service you eventually choose may have its own procedure.

auth.js
module.exports = {
  secret: "thisisasecret",
  mail: {
    user: "teochewthunder@gmail.com",
    password: "xxxx xxxx xxxx xxxx"
  }

}


Now create the options object. The properties from and to are email addresses. from will be the email address you send from, so I use my own email address. to is the email address specified n the form submitted by the user.

app.js
app.post("/process", (req, res)=> {
  var nodemailer = require("nodemailer");

  var transport = nodemailer.createTransport({
    service: "Gmail",
    host: "smtp.gmail.com",
    secure: true,
    port: 465,
    auth: {
      user: auth.mail.user,
      pass: auth.mail.password
    }
  });

  var options = {
    from: "teochwthunder@gmail.com",
    to: req.body.txtEmail,
    subject: ,
    text:
  };


  console.log(req.body);
  res.redirect(303, "/thankyou");
});


subject can be any text. I'm just going to keep it simple. And text is the content specified in the form.

app.js
var options = {
  from: "teochwthunder@gmail.com",
  to: req.body.txtEmail,
  subject: "A Christmas message from your friend has arrived!",
  text: req.body.txtMessage
};


After that, we use the sendMail() method of the transport object. The first argument is options, since this tells nodemailer the particulars of who and what to send. The second is a callback with the parameter error.

app.js
var options = {
  from: "teochwthunder@gmail.com",
  to: req.body.txtEmail,
  subject: "A Christmas message from your friend has arrived!",
  text: req.body.txtMessage
};

transport.sendMail(options, (error) => {

});  


console.log(req.body);
res.redirect(303, "/thankyou");


If error exists, we handle it the way we've handled code 500 errors. Otherwise, we redirect to the thankyou route. Which also means we will no longer be needing the code below that, so comment it off.

app.js
transport.sendMail(options, (error) => {
  if (error) {
    res.render("500", { errorMessage: error.code });
  } else {
    res.redirect(303, "/thankyou");
  }
});  

console.log(req.body);
//res.redirect(303, "/thankyou");


Let's try this.


This is the email!


HTML Email

Let's take this a step further. We've already nailed down the process of sending email. Now, we want to render the email in HTML. For this, we create a layout, emailtemplate.handlebars. This has a table-based layout and inline CSS - really retro stuff because that's the safest way in email browsers. Note that the style attributes have been left as blank strings.

views/emailtemplate.handlebars
<table border="0" width="500px" cellpadding="5" cellspacing="0" style="font-size:14px;text-align: center;">
  <tr>
    <td colspan = "2" style="">
      <h1 style="">MERRY CHRISTMAS!</h1>
    </td>
  </tr>

  <tr>
    <td style="" width="70%">
      <p style=""></p>
    </td>
    <td style="">
      <table cellpadding="5" cellspacing="0">
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
          <td style="">&#9733;</td>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
        </tr>
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
          <td style="">&#9679;</td>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
        </tr>
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style=""></td>
          <td style=""></td>
        </tr>
        <tr>
          <td style=""></td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style=""></td>
        </tr>
        <tr>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
          <td style="">&#9679;</td>
        </tr>
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
          <td style="">&nbsp;</td>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
        </tr>        
      </table>
    </td>
  </tr>
  <tr>
    <td colspan = "2" style="">
      <p style="">- Teochew Thunder</p>
    </td>
  </tr>
</table>


Now let's add placeholders for content and variables such as colors0, colors1, colors2, colors3, colors4.

views/emailtemplate.handlebars
<table border="0" width="500px" cellpadding="5" cellspacing="0" style="font-size:14px;text-align: center;">
  <tr>
    <td colspan = "2" style="background-color:{{ colors0 }};">
      <h1 style="color:{{ colors4 }};">MERRY CHRISTMAS!</h1>
    </td>
  </tr>

  <tr>
    <td style="background-color:{{ colors1 }};" width="70%">
      <p style="color:{{ colors4 }};">{{{ message }}}</p>
    </td>
    <td style="background-color:{{ colors4 }};text-align:center;">
      <table cellpadding="5" cellspacing="0">
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
          <td style="color:#FFFF00;">&#9733;</td>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
        </tr>
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
        </tr>
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors2 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
          <td style=""></td>
          <td style=""></td>
        </tr>
        <tr>
          <td style=""></td>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors2 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors3 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors2 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
          <td style=""></td>
        </tr>
        <tr>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors2 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors3 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors2 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors3 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors2 }};font-size:2em;">&#9679;</td>
          <td style="color:{{ colors1 }};font-size:2em;">&#9679;</td>
        </tr>
        <tr>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
          <td style="background-color:{{ colors0 }};">&nbsp;</td>
          <td style=""></td>
          <td style=""></td>
          <td style=""></td>
        </tr>        
      </table>
    </td>
  </tr>
  <tr>
    <td colspan = "2" style="background-color:{{ colors0 }};text-align: right;">
      <p style="color:{{ colors4 }};">- Teochew Thunder</p>
    </td>
  </tr>
</table>


Back to the process route, we define layoutColors as an object with three properties - red, green and blue. Each is an array containing hex values. Basically they are shades of red, green and blue.

app.js
var transport = nodemailer.createTransport({
  service: "Gmail",
  host: "smtp.gmail.com",
  secure: true,
  port: 465,
  auth: {
    user: auth.mail.user,
    pass: auth.mail.password
  }
});

var layoutColors = {
  red: ["#440000", "#AA0000", "#FF0000", "#FFAAAA", "#FFCCCC"],
  green: ["#004400", "#00AA00", "#00FF00", "#AAFFAA", "#CCFFCC"],
  blue: ["#000044", "#0000AA", "#0000FF", "#AAAAFF", "#CCCCFF"],
};


var options = {
  from: "teochwthunder@gmail.com",
  to: req.body.txtEmail,
  subject: "A Christmas message from your friend has arrived!",
  text: req.body.txtMessage
};


We then use render() to create the HTML using the template we defined earlier, with an object that we will fill in soon. After rendering, we have a callback, where html is the HTML rendered. In it, we first check if error exists, and handle it if so. Otherwise, we go to the code we wrote earlier.

app.js
var layoutColors = {
  red: ["#440000", "#AA0000", "#FF0000", "#FFAAAA", "#FFCCCC"],
  green: ["#004400", "#00AA00", "#00FF00", "#AAFFAA", "#CCFFCC"],
  blue: ["#000044", "#0000AA", "#0000FF", "#AAAAFF", "#CCCCFF"],
};

res.render("emailtemplate", {}, (error, html) => {
  if (error) {
    console.log(error);
    res.render("500", { errorMessage: error.code });
  } else {

    var options = {
      from: "teochwthunder@gmail.com",
      to: req.body.txtEmail,
      subject: "A Christmas message from your friend has arrived!",
      text: req.body.txtMessage
    };
  
    transport.sendMail(options, (error) => {
      if (error) {
        console.log(error);
        res.render("500", { errorMessage: error.code });
      } else {
        res.redirect(303, "/thankyou");
      }
    });
  }  
});


Then we fill in the object that will populate values in the template. Layout is null, because there is no layout - we're using emailtemplates.handlebars by itself. We determine colors0, colors1, colors2, colors3 and colors4 based on the value of the drop-down list submitted in the form. And message is the value of txtMessage, with the line breaks replaced with HTML breaks.

app.js
var layoutColors = {
  red: ["#440000", "#AA0000", "#FF0000", "#FFAAAA", "#FFCCCC"],
  green: ["#004400", "#00AA00", "#00FF00", "#AAFFAA", "#CCFFCC"],
  blue: ["#000044", "#0000AA", "#0000FF", "#AAAAFF", "#CCCCFF"],
};

res.render("emailtemplate", {
  layout: null,
  colors0: layoutColors[req.body.ddlLayout][0],
  colors1: layoutColors[req.body.ddlLayout][1],
  colors2: layoutColors[req.body.ddlLayout][2],
  colors3: layoutColors[req.body.ddlLayout][3],
  colors4: layoutColors[req.body.ddlLayout][4],
  message: req.body.txtMessage.split("\n").join("<br />")

}, (error, html) => {
  if (error) {
    console.log(error);
    res.render("500", { errorMessage: error.code });
  } else {
    var options = {
      from: "teochwthunder@gmail.com",
      to: req.body.txtEmail,
      subject: "A Christmas message from your friend has arrived!",
      text: req.body.txtMessage
    };
  
    transport.sendMail(options, (error) => {
      if (error) {
        console.log(error);
        res.render("500", { errorMessage: error.code });
      } else {
        res.redirect(303, "/thankyou");
      }
    });
  }
});


Except that the mailing code now has HTML instead of text. And we use the html value instead of the text value of txtMessage.

app.js
var options = {
  from: "teochwthunder@gmail.com",
  to: req.body.txtEmail,
  subject: "A Christmas message from your friend has arrived!",
  html: html
};


Now let's send the form again.


Try the green layout.


And the blue.


Merry Christmas!

This wasn't my fanciest work, I'm afraid. Just wanted to feel my way around NodeJS. But we all have to start somewhere!

Try Nodemailer today. Yule love it!
T___T