Creating email invites and sending them to your guest list can be an expensive and daunting task. In this article, we review how can leverage existing services to dynamically create wedding invites and send them to an invite list as easily as possible
The final project demo can be viewed on Codesandbox.
We are first going to create a new Nuxt.Js project. Nuxt.Js is an intuitive Vue.Js framework hailed for being modular performant and enjoyable.
In your desired work directory, open up your terminal of choice and run the following command:
yarn create nuxt-app nuxtjs-email-wedding-invitation
# OR
npx create-nuxt-app nuxtjs-email-wedding-invitation
# OR
npm init nuxt-app nuxtjs-email-wedding-invitation
Code language: PHP (php)
You will then receive a set of questions to help the installer configure your project. Here are our recommendations:
Project name: nuxtjs-email-wedding-invitation
Programming language: JavaScript
Package manager: Yarn
UI framework: Tailwind CSS
Nuxt.js modules: None
Linting tools: None
Testing framework: None
Rendering mode: Universal (SSR / SSG)
Deployment target: Server (Node.Js hosting)
Development tools: None
Version control: Git
You may now enter the project directory and run the project:
cd nuxtjs-email-wedding-invitation
yarn dev
#OR
npm run dev
Code language: CSS (css)
To dynamically create the invites, we are going to use Cloudinary as our platform of choice.
Cloudinary aims to help unleash media’s full potential in companies by proving a powerful media developer experience.
To install, we will use the nuxt/cloudinary package. Run the following command in the project directory:
yarn add @nuxtjs/cloudinary
# OR
npm install @nuxtjs/cloudinary
Code language: CSS (css)
We will then add @nuxtjs/cloudinary
as a module in the modules section of nuxt.config.js
:
// nuxt.config.js
export default {
...
modules:[
'@nuxtjs/cloudinary'
]
}
Code language: JavaScript (javascript)
To configure the module, we will create a cloudinary
section in nuxt.config.js
.
// nuxt.config.js
export default{
...
cloudinary: {
cloudName: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME,
secure: true,
useComponent: true
}
}
Code language: JavaScript (javascript)
The above code snippet utilizes environmental variables to retrieve the cloudName
. Environmental variables are values we configure outside our codebase based on the environment our code is in. These can be sensitive details such as login credentials.
We are going to create a .env
file at the root of our project to store our environmental variables. Nuxt.Js will automatically load these variables as long as they are prefixed with NUXT_ENV
cat .env
Code language: CSS (css)
<!-- .env -->
NUXT_ENV_CLOUDINARY_CLOUD_NAME = secret-cloud-name
Code language: HTML, XML (xml)
Change the secret-cloud-name
to your Cloudinary
cloud name. If you don’t have one, simply create an account on Cloudinary and refer to the console section.
We will be using some basic forms in this project. @tailwind/forms
provides some basic styling for the default form element types.
To install it, run the following command:
yarn add @tailwindcss/forms
# OR
npm install @tailwindcss/forms
Code language: CSS (css)
We installed TailwindCSS into our project during the setup configurations. To publish your tailwind.config.js
file, run the following command:
npx tailwindcss init
We are now going to add tailwind forms to the required plugins section of the tailwind.config.js
file.
module.exports = {
theme: {
// ...
},
plugins: [
require('@tailwindcss/forms'),
// ...
],
}
Code language: JavaScript (javascript)
For easy date manipulation and formatting, we will use momentjs. You may install it by running the following command:
yarn add moment
# OR
npm install moment
Code language: PHP (php)
To send the emails, we will an Express.Js. This is a fast, unopinionated, minimalist web framework for Node.js.
To install, run the following command:
yarn add express
# OR
npm install express
Code language: PHP (php)
Nodemailer is a module for Node.Js applications to easily send emails.
Installation is simple, run the following command:
yarn add nodemailer
# OR
npm install nodemailer
Code language: PHP (php)
Vuex is a state management library for Vuex applications.
To create a vuex store, we will create an index.js
file in the store
folder.
cd store
touch index.js
Code language: CSS (css)
The state is the single source of truth for our applications. It is typically defined as a single object. Here is the state we will define in our store:
// store/index.js
export const state = () => ({
names: {
bride: "Eve",
groom: "Adam"
},
date: {
day: "SATURDAY",
month: "AUG",
date: 17,
year: 2022,
time: "4 PM"
},
address: {
first: "Avocado Tree - Volcano of Trust",
second: "Garden of Eden - Mesopotamia"
}
});
Code language: JavaScript (javascript)
Getters allow us to retrieve and compute derived state from store data.
We are going to define the following getters.
// store/index.js
export const getters = {
names: state => state.names,
date: state => state.date,
address: state => state.address
};
Code language: JavaScript (javascript)
Mutations change state in a Vuex store. They are committed in actions. We will define mutations to change the couple’s names, the date, and the address of the wedding:
// store/index.js
export const mutations = {
updateNames(state, names) {
state.names = names;
return state.names;
},
updateDates(state, weddingDate, weddingTime) {
const date = moment(weddingDate);
state.date.day = date.format('dddd').toUpperCase();
state.date.month = date.format('MMM').toUpperCase();
state.date.date = date.format('D');
state.date.year = date.format('YYYY');
state.date.time = moment(weddingTime).format('H A');
return state.date;
},
updateAddress(state, address) {
state.address.first = address.first;
state.address.second = address.second;
return state.address;
}
};
Code language: JavaScript (javascript)
In the above code snippet, we use date formating to extract the date elements we need. You can read more about date formating with moment.js here.
Actions allow us to commit mutations. We will define one action to commit all our mutations:
// store/index.js
export const actions = {
storeWeddingDetails({ commit }, formData) {
commit('updateNames', formData.names);
commit('updateDates', formData.date, formData.time);
return commit('updateAddress', formData.address);
}
};
Code language: JavaScript (javascript)
With the store above intact, we then create a simple form to dispatch the storeWeddingDetails
action with new wedding details. Feel free to check out how we achieve this here: Github – index.vue
We are now going to work towards rendering the templates using Cloudinary.
Download this file and add it to a new nuxtjs-image-wedding-invitation
directory in your Cloudinary media library. Ensure it is named v722-aum-35a
. Thus the complete public_id should be nuxtjs-image-wedding-invitation/v722-aum-35a
.
The main transformation we are going to be using is the text overlay transformation. Here is a code sample:
<!-- components/Invite.vue -->
...
<cld-transformation
:overlay="`text:Sacramento_600_normal:${names.bride},co_rgb:000000`"
gravity="center"
y="-1900"
/>
...
Code language: HTML, XML (xml)
The above transformation ensures the following settings are applied:
-
Overlay is a text overlay
-
Font used is the Sacramento Google Font.
-
Text size is 600px
-
Font weight is normal
-
Content is the bride’s name from the Vue.Js component state
-
Color is black (#000000)
-
Overlay is positioned relative to the center of the image
-
Overlay is shifted left by 1900px (-1900 on the Y-Axis)
We are going to apply similar transformations to our template to fully render the invite. Here is the full file:
<!-- components/Invite.vue -->
<template>
<cld-image
public-id="nuxtjs-image-wedding-invitation/v722-aum-35a"
crop="fill"
alt="Wedding card"
>
<!-- Bride Name -->
<cld-transformation
:overlay="`text:Sacramento_600_normal:${names.bride},co_rgb:000000`"
gravity="center"
y="-1900"
/>
<!-- And -->
<cld-transformation
overlay="text:Roboto_100_normal:AND,co_rgb:000000"
gravity="center"
y="-1300"
/>
<!-- Husband Name -->
<cld-transformation
:overlay="`text:Sacramento_600_normal:${names.groom},co_rgb:000000`"
gravity="center"
y="-700"
/>
<!-- Invitation Text -->
<cld-transformation
overlay="text:Roboto_100_normal:TOGETHER WITH THEIR FAMILIES,co_rgb:000000"
gravity="center"
/>
<cld-transformation
overlay="text:Roboto_100_normal:INVITE YOU TO THEIR WEDDING CELEBRATION,co_rgb:000000"
gravity="center"
y="200"
/>
<!-- Day -->
<cld-transformation
:overlay="`text:Roboto_100_normal:${date.day},co_rgb:000000`"
y="800"
x="-600"
/>
<!-- Month -->
<cld-transformation
:overlay="`text:Roboto_100_normal:${date.month},co_rgb:000000`"
gravity="center"
y="500"
/>
<!-- Date -->
<cld-transformation
:overlay="`text:Roboto_100_normal:${date.year},co_rgb:000000`"
gravity="center"
y="1100"
/>
<!-- Year -->
<cld-transformation
:overlay="`text:Roboto_400_normal:${date.date},co_rgb:000000`"
gravity="center"
y="800"
/>
<!-- Time -->
<cld-transformation
:overlay="`text:Roboto_100_normal:AT ${date.time},co_rgb:000000`"
y="800"
x="500"
/>
<!-- Location -->
<cld-transformation
:overlay="`text:Roboto_100_normal:${address.first},co_rgb:000000`"
y="1500"
/>
<cld-transformation
:overlay="`text:Roboto_100_normal:${address.second},co_rgb:000000`"
y="1700"
/>
<!-- Reception -->
<cld-transformation
overlay="text:Sacramento_200_normal:reception to follow,co_rgb:000000"
y="2000"
/>
</cld-image>
</template>
<script>
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters({
names: "names",
date: "date",
address: "address",
}),
},
};
</script>
Code language: HTML, XML (xml)
The above component will get the invite details from our Vuex store using the mapGetters helper. We use it to map store getters to local computed properties.
We will then use the transformations above to render the text onto the invite template.
To send the invites, we will utilize server middlewares in Nuxt.Js. This allows us to define our own middleware which will be run when Nuxt.Js is run.
First, let’s create the middleware file:
mkdir server-middleware
cat api.js
Code language: CSS (css)
We are then going to update our nuxt.config.js
to recognize and start our middleware.
// nuxt.config.js
export default {
....
serverMiddleware: [
{
path: "/api",
handler: "~/server-middleware/api.js"
},
],
...
}
Code language: JavaScript (javascript)
The above snippet will ensure that any requests to the /api
path are routed to the ~/server-middleware/api.js
handler.
Let us add some boilerplate code to ensure our api receives requests from the /api/send-email
path.
// server-middleware/api.js
const app = require('express')()
app.all('/send-email', async (req, res) => {
res.json({ sent: true })
})
module.exports = app
Code language: JavaScript (javascript)
Our handler will need to access json data in the request body. We are going to use Express.Js’s inbuilt json parser middlware.
Add the following code before the app.all
declaration
// server-middleware/api.js
const app = require('express')()
// Add the next two lines
const express = require('express')
app.use(express.json())
app.all('/send-email', async (req, res) => {
Code language: JavaScript (javascript)
For Nodemailer to send emails, we will need to set up our Gmail credentials. Feel free to create an account if you do not have one. To be able to send emails using Nodemailer, you’ll need to enable less secure access. Feel free to follow this guide. We advise you to create a separate email account as enabling less secure access makes your account more vulnerable to attacks and misuse.
Add MAIL_USERNAME
and MAIL_PASSWORD
to your .env file
<!-- .env -->
...
MAIL_USERNAME = example@gmail.com
MAIL_PASSWORD = very-secret-strong-password
Code language: HTML, XML (xml)
We will now configure nodemailer to send our emails when the route is called. Here is the entire file compiled together.
// server-middleware/api.js
require('dotenv').config()
const app = require('express')()
const express = require('express')
app.use(express.json())
const nodemailer = require('nodemailer');
app.all('/send-email', async (req, res) => {
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.MAIL_USERNAME,
pass: process.env.MAIL_PASSWORD,
}
});
const body = req.body;
let text = `Hello ${body.to.name},\n\n`;
text += "We would like to invite you to our wedding ceremony. Please find attached the invitation for more details.\n\n";
text += "Looking forward to seeing you there!\n\n";
text += "Best wishes,\n";
text += `${body.names.bride} and ${body.names.groom}`;
var mailOptions = {
from: process.env.MAIL_USERNAME,
to: body.to.email,
subject: `Wedding Invitation from ${body.names.bride} and ${body.names.groom}`,
text,
attachments: [
{
filename: `${body.names.bride}-and-${body.names.groom}-wedding-invite.jpg`,
path: body.invite
},
]
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
res.json({ sent: true })
})
module.exports = app
Code language: JavaScript (javascript)
The above endpoint does not have any authorization or authentication. For large-scale usage, we advise you to add these and more to ensure your mail service is not misused.
Our middlware is now on standby ready to send emails to our invitees. Let us prepare the data that needs to be sent.
First, we need to get the URL to our invite image. To achieve this, we will get the invite details from our vuex store, render the invite, and then use a recursive method to get the URL.
<!-- pages/invite.vue -->
<template>
...
<Invite id="invite-container" />
...
</template>
<script>
import { mapGetters } from "vuex";
export default {
...
computed: {
...mapGetters({
names: "names",
date: "date",
address: "address",
}),
},
mounted() {
this.getImageURL();
},
methods: {
getImageURL() {
const image = document.getElementById("invite-container");
if (image === null) {
setTimeout(() => {
this.getImageURL();
}, 1000);
return;
}
if (image.src) {
this.image = image.src;
}
if (this.image == null) {
setTimeout(() => {
this.getImageURL();
}, 1000);
return;
}
}
...
},
};
</script>
Code language: HTML, XML (xml)
Let us create a simple form to collect invitee details from the user interface. Once the data has been collected, we will send it to the API endpoint and add the invitee to the invitees’ table for UI confirmation.
<!-- pages/invite.vue -->
<template>
<form
action="#"
method="POST"
@submit.prevent="submit"
>
<div>
<label for="name" >
Name
</label>
<div >
<input
id="name"
name="name"
type="text"
autocomplete="name"
required=""
v-model="form.name"
/>
</div>
</div>
<div>
<label for="email" >
Email address
</label>
<div class="mt-1">
<input
id="email"
name="email"
type="email"
autocomplete="email"
required=""
v-model="form.email"
/>
</div>
</div>
<div>
<button
type="submit"
:disabled="image == null"
>
Send Invite
</button>
</div>
</form>
</template>
<script>
export default {
data() {
return {
form: {
name: "",
email: "",
},
invitees: [],
image: null,
};
},
...
methods: {
...
async submit() {
const submitData = {
to: {
name: this.form.name,
email: this.form.email,
},
names: {
bride: this.names.bride,
groom: this.names.groom,
},
invite: this.image,
};
const response = await fetch("/api/send-email", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(submitData),
});
let jsonResponse = response.json();
console.log(jsonResponse);
this.invitees.push({
name: this.form.name,
email: this.form.email,
});
},
},
};
</script>
Code language: HTML, XML (xml)
From the above, we have now learned how to prepare and send email invites to our wedding guests in a less stressful manner.
To improve the project, feel free to find a template of your preference, customize the invite contents further, or even link the email sending to a mailing list service for easy guest list management. Let us know if you have any questions or suggestions.
You can find the full code on my Github. https://github.com/musebe/Nuxtjs-wedding-invitation.git
For Further reading, Visit :