diff --git a/.env.example b/.env.example
index 9fa0bef4..1a77d8a3 100644
--- a/.env.example
+++ b/.env.example
@@ -7,4 +7,20 @@ NEXT_PUBLIC_TELEMETRY_KEY=js.2pvs2bbpqq1zxna97wcml.oi2jzirnbj1ev4tc57c5r
 
 # Used for the Office 365 / Outlook.com Calendar integration
 MS_GRAPH_CLIENT_ID=
-MS_GRAPH_CLIENT_SECRET=
\ No newline at end of file
+MS_GRAPH_CLIENT_SECRET=
+
+# E-mail settings
+
+# Calendso uses nodemailer (@see https://nodemailer.com/about/) to provide email sending. As such we are trying to
+# allow access to the nodemailer transports from the .env file. E-mail templates are accessible within lib/emails/
+
+# Configures the global From: header whilst sending emails.
+EMAIL_FROM='Calendso <notifications@yourselfhostedcalendso.com>'
+
+# Configure SMTP settings (@see https://nodemailer.com/smtp/).
+# Note: The below configuration for Office 365 has been verified to work.
+EMAIL_SERVER_HOST='smtp.office365.com'
+EMAIL_SERVER_PORT=587
+EMAIL_SERVER_USER='<office365_emailAddress>'
+# Keep in mind that if you have 2FA enabled, you will need to provision an App Password.
+EMAIL_SERVER_PASSWORD='<office365_password>'
diff --git a/lib/calendarClient.ts b/lib/calendarClient.ts
index a90f0037..fe1e7111 100644
--- a/lib/calendarClient.ts
+++ b/lib/calendarClient.ts
@@ -1,6 +1,6 @@
 
 const {google} = require('googleapis');
-const credentials = process.env.GOOGLE_API_CREDENTIALS;
+import createNewEventEmail from "./emails/new-event";
 
 const googleAuth = () => {
     const {client_secret, client_id, redirect_uris} = JSON.parse(process.env.GOOGLE_API_CREDENTIALS).web;
@@ -43,18 +43,24 @@ const o365Auth = (credential) => {
     };
 };
 
+interface Person { name?: string, email: string, timeZone: string }
 interface CalendarEvent {
+    type: string;
     title: string;
     startTime: string;
-    timeZone: string;
     endTime: string;
     description?: string;
     location?: string;
-    organizer: { name?: string, email: string };
-    attendees: { name?: string, email: string }[];
+    organizer: Person;
+    attendees: Person[];
 };
 
-const MicrosoftOffice365Calendar = (credential) => {
+interface CalendarApiAdapter {
+    createEvent(event: CalendarEvent): Promise<any>;
+    getAvailability(dateFrom, dateTo): Promise<any>;
+}
+
+const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => {
 
     const auth = o365Auth(credential);
 
@@ -73,11 +79,11 @@ const MicrosoftOffice365Calendar = (credential) => {
             },
             start: {
                 dateTime: event.startTime,
-                timeZone: event.timeZone,
+                timeZone: event.organizer.timeZone,
             },
             end: {
                 dateTime: event.endTime,
-                timeZone: event.timeZone,
+                timeZone: event.organizer.timeZone,
             },
             attendees: event.attendees.map(attendee => ({
                 emailAddress: {
@@ -133,7 +139,7 @@ const MicrosoftOffice365Calendar = (credential) => {
     }
 };
 
-const GoogleCalendar = (credential) => {
+const GoogleCalendar = (credential): CalendarApiAdapter => {
     const myGoogleAuth = googleAuth();
     myGoogleAuth.setCredentials(credential.key);
     return {
@@ -170,11 +176,11 @@ const GoogleCalendar = (credential) => {
                 description: event.description,
                 start: {
                     dateTime: event.startTime,
-                    timeZone: event.timeZone,
+                    timeZone: event.organizer.timeZone,
                 },
                 end: {
                     dateTime: event.endTime,
-                    timeZone: event.timeZone,
+                    timeZone: event.organizer.timeZone,
                 },
                 attendees: event.attendees,
                 reminders: {
@@ -206,7 +212,7 @@ const GoogleCalendar = (credential) => {
 };
 
 // factory
-const calendars = (withCredentials): [] => withCredentials.map( (cred) => {
+const calendars = (withCredentials): CalendarApiAdapter[] => withCredentials.map( (cred) => {
     switch(cred.type) {
         case 'google_calendar': return GoogleCalendar(cred);
         case 'office365_calendar': return MicrosoftOffice365Calendar(cred);
@@ -219,9 +225,17 @@ const calendars = (withCredentials): [] => withCredentials.map( (cred) => {
 const getBusyTimes = (withCredentials, dateFrom, dateTo) => Promise.all(
     calendars(withCredentials).map( c => c.getAvailability(dateFrom, dateTo) )
 ).then(
-    (results) => results.reduce( (acc, availability) => acc.concat(availability) )
+    (results) => results.reduce( (acc, availability) => acc.concat(availability), [])
 );
 
-const createEvent = (credential, evt: CalendarEvent) => calendars([ credential ])[0].createEvent(evt);
+const createEvent = (credential, calEvent: CalendarEvent) => {
+    if (credential) {
+        return calendars([credential])[0].createEvent(calEvent);
+    }
+    // send email if no Calendar integration is found for now.
+    createNewEventEmail(
+      calEvent,
+    );
+};
 
 export { getBusyTimes, createEvent, CalendarEvent };
diff --git a/lib/emails/confirm-booked.ts b/lib/emails/confirm-booked.ts
new file mode 100644
index 00000000..92595f0a
--- /dev/null
+++ b/lib/emails/confirm-booked.ts
@@ -0,0 +1,65 @@
+
+import nodemailer from 'nodemailer';
+import { serverConfig } from "../serverConfig";
+import { CalendarEvent } from "../calendarClient";
+import dayjs, { Dayjs } from "dayjs";
+import localizedFormat from "dayjs/plugin/localizedFormat";
+import utc from "dayjs/plugin/utc";
+import timezone from "dayjs/plugin/timezone";
+
+dayjs.extend(localizedFormat);
+dayjs.extend(utc);
+dayjs.extend(timezone);
+
+export default function createConfirmBookedEmail(calEvent: CalendarEvent, options: any = {}) {
+  return sendEmail(calEvent, {
+    provider: {
+      transport: serverConfig.transport,
+      from: serverConfig.from,
+    },
+    ...options
+  });
+}
+
+const sendEmail = (calEvent: CalendarEvent, {
+  provider,
+}) => new Promise( (resolve, reject) => {
+
+  const { from, transport } = provider;
+  const inviteeStart: Dayjs = <Dayjs>dayjs(calEvent.startTime).tz(calEvent.attendees[0].timeZone);
+
+  nodemailer.createTransport(transport).sendMail(
+    {
+      to: `${calEvent.attendees[0].name} <${calEvent.attendees[0].email}>`,
+      from,
+      subject: `Confirmed: ${calEvent.type} with ${calEvent.organizer.name} on ${inviteeStart.format('dddd, LL')}`,
+      html: html(calEvent),
+      text: text(calEvent),
+    },
+    (error, info) => {
+      console.log(info);
+      if (error) {
+        console.error("SEND_BOOKING_CONFIRMATION_ERROR", calEvent.attendees[0].email, error);
+        return reject(new Error(error));
+      }
+      return resolve();
+    }
+  )
+});
+
+const html = (calEvent: CalendarEvent) => {
+  const inviteeStart: Dayjs = <Dayjs>dayjs(calEvent.startTime).tz(calEvent.attendees[0].timeZone);
+  return `
+    <div>
+      Hi ${calEvent.attendees[0].name},<br />
+      <br />
+      Your ${calEvent.type} with ${calEvent.organizer.name} at ${inviteeStart.format('h:mma')} 
+      (${calEvent.attendees[0].timeZone}) on ${inviteeStart.format('dddd, LL')} is scheduled.<br />
+      <br />
+      Additional notes:<br />
+      ${calEvent.description}
+    </div>
+  `;
+};
+
+const text = (evt: CalendarEvent) => html(evt).replace('<br />', "\n").replace(/<[^>]+>/g, '');
\ No newline at end of file
diff --git a/lib/emails/new-event.ts b/lib/emails/new-event.ts
new file mode 100644
index 00000000..dfb65e80
--- /dev/null
+++ b/lib/emails/new-event.ts
@@ -0,0 +1,105 @@
+
+import nodemailer from 'nodemailer';
+import dayjs, { Dayjs } from "dayjs";
+import localizedFormat from 'dayjs/plugin/localizedFormat';
+import utc from 'dayjs/plugin/utc';
+import timezone from 'dayjs/plugin/timezone';
+import { createEvent } from 'ics';
+import { CalendarEvent } from '../calendarClient';
+import { serverConfig } from '../serverConfig';
+
+dayjs.extend(localizedFormat);
+dayjs.extend(utc);
+dayjs.extend(timezone);
+
+export default function createNewEventEmail(calEvent: CalendarEvent, options: any = {}) {
+  return sendEmail(calEvent, {
+    provider: {
+      transport: serverConfig.transport,
+      from: serverConfig.from,
+    },
+    ...options
+  });
+}
+
+// converts "2021-05-27T16:59:09+01:00" to [ 2021, 5, 27, 15, 59, 9 ]
+const convertIsoDateToUtcDateArr = (isoDate: string): [] => {
+  const isoUtcDate: string = dayjs(isoDate).utc().format();
+  return Array.prototype.concat(
+    ...isoUtcDate.substr(0, isoUtcDate.indexOf('+')).split('T')
+      .map(
+        (parts) => parts.split('-').length > 1 ? parts.split('-').map(
+          (n) => parseInt(n, 10)
+        ) : parts.split(':').map(
+          (n) => parseInt(n, 10)
+        )
+      ));
+}
+
+
+const icalEventAsString = (calEvent: CalendarEvent): string => {
+  const icsEvent = createEvent({
+    start: convertIsoDateToUtcDateArr(calEvent.startTime),
+    startInputType: 'utc',
+    productId: 'calendso/ics',
+    title: `${calEvent.type} with ${calEvent.attendees[0].name}`,
+    description: calEvent.description,
+    duration: { minutes: dayjs(calEvent.endTime).diff(dayjs(calEvent.startTime), 'minute') },
+    organizer: { name: calEvent.organizer.name, email: calEvent.organizer.email },
+    attendees: calEvent.attendees.map( (attendee: any) => ({ name: attendee.name, email: attendee.email }) ),
+    status: "CONFIRMED",
+  });
+  if (icsEvent.error) {
+    throw icsEvent.error;
+  }
+  return icsEvent.value;
+}
+
+const sendEmail = (calEvent: CalendarEvent, {
+  provider,
+}) => new Promise( (resolve, reject) => {
+  const { transport, from } = provider;
+  const organizerStart: Dayjs = <Dayjs>dayjs(calEvent.startTime).tz(calEvent.organizer.timeZone);
+  nodemailer.createTransport(transport).sendMail(
+    {
+      icalEvent: {
+        filename: 'event.ics',
+        content: icalEventAsString(calEvent),
+      },
+      from,
+      to: calEvent.organizer.email,
+      subject: `New event: ${calEvent.attendees[0].name} - ${organizerStart.format('LT dddd, LL')} - ${calEvent.type}`,
+      html: html(calEvent),
+      text: text(calEvent),
+    },
+    (error) => {
+      if (error) {
+        console.error("SEND_NEW_EVENT_NOTIFICATION_ERROR", calEvent.organizer.email, error);
+        return reject(new Error(error));
+      }
+      return resolve();
+    });
+});
+
+const html = (evt: CalendarEvent) => `
+  <div>
+    Hi ${evt.organizer.name},<br />
+    <br />
+    A new event has been scheduled.<br />
+    <br />
+    <strong>Event Type:</strong><br />
+    ${evt.type}<br />
+    <br />
+    <strong>Invitee Email:</strong><br />
+    <a href=\\"mailto:${evt.attendees[0].email}\\">${evt.attendees[0].email}</a><br />
+    <br />
+    <strong>Invitee Time Zone:</strong><br />
+    ${evt.attendees[0].timeZone}<br />
+    <br />
+    <strong>Additional notes:</strong><br />
+    ${evt.description}
+  </div>
+`;
+
+// just strip all HTML and convert <br /> to \n
+const text = (evt: CalendarEvent) => html(evt).replace('<br />', "\n").replace(/<[^>]+>/g, '');
\ No newline at end of file
diff --git a/lib/serverConfig.ts b/lib/serverConfig.ts
new file mode 100644
index 00000000..2676193c
--- /dev/null
+++ b/lib/serverConfig.ts
@@ -0,0 +1,33 @@
+
+function detectTransport(): string | any {
+
+  if (process.env.EMAIL_SERVER) {
+    return process.env.EMAIL_SERVER;
+  }
+
+  if (process.env.EMAIL_SERVER_HOST) {
+    const port = parseInt(process.env.EMAIL_SERVER_PORT);
+    const transport = {
+      host: process.env.EMAIL_SERVER_HOST,
+      port,
+      auth: {
+        user: process.env.EMAIL_SERVER_USER,
+        pass: process.env.EMAIL_SERVER_PASSWORD,
+      },
+      secure: (port === 465),
+    };
+
+    return transport;
+  }
+
+  return {
+    sendmail: true,
+    newline: 'unix',
+    path: '/usr/sbin/sendmail'
+  };
+}
+
+export const serverConfig = {
+  transport: detectTransport(),
+  from: process.env.EMAIL_FROM,
+};
\ No newline at end of file
diff --git a/next.config.js b/next.config.js
index 4b172ebd..e8b36e89 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,5 +1,10 @@
+
 const withTM = require('next-transpile-modules')(['react-timezone-select']);
 
+if ( ! process.env.EMAIL_FROM ) {
+    console.warn('\x1b[33mwarn', '\x1b[0m', 'EMAIL_FROM environment variable is not set, this may indicate mailing is currently disabled. Please refer to the .env.example file.');
+}
+
 const validJson = (jsonString) => {
     try {
         const o = JSON.parse(jsonString);
diff --git a/package.json b/package.json
index 2a058503..d6053219 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
     "next": "^10.2.0",
     "next-auth": "^3.13.2",
     "next-transpile-modules": "^7.0.0",
+    "nodemailer": "^6.6.1",
     "react": "17.0.1",
     "react-dom": "17.0.1",
     "react-phone-number-input": "^3.1.21",
diff --git a/pages/[user]/book.tsx b/pages/[user]/book.tsx
index c84d0ab2..9fd7d5fd 100644
--- a/pages/[user]/book.tsx
+++ b/pages/[user]/book.tsx
@@ -53,7 +53,9 @@ export default function Book(props) {
             end: dayjs(date).add(props.eventType.length, 'minute').format(),
             name: event.target.name.value,
             email: event.target.email.value,
-            notes: event.target.notes.value
+            notes: event.target.notes.value,
+            timeZone: preferredTimeZone,
+            eventName: props.eventType.title,
         };
 
         if (selectedLocation) {
diff --git a/pages/api/book/[user].ts b/pages/api/book/[user].ts
index d832901c..9d55b157 100644
--- a/pages/api/book/[user].ts
+++ b/pages/api/book/[user].ts
@@ -1,6 +1,7 @@
 import type { NextApiRequest, NextApiResponse } from 'next';
 import prisma from '../../../lib/prisma';
 import { createEvent, CalendarEvent } from '../../../lib/calendarClient';
+import createConfirmBookedEmail from "../../../lib/emails/confirm-booked";
 
 export default async function handler(req: NextApiRequest, res: NextApiResponse) {
     const { user } = req.query;
@@ -12,22 +13,30 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
         select: {
             credentials: true,
             timeZone: true,
+            email: true,
+            name: true,
         }
     });
 
     const evt: CalendarEvent = {
-        title: 'Meeting with ' + req.body.name,
+        type: req.body.eventName,
+        title: req.body.eventName + ' with ' + req.body.name,
         description: req.body.notes,
         startTime: req.body.start,
         endTime: req.body.end,
-        timeZone: currentUser.timeZone,
         location: req.body.location,
+        organizer: { email: currentUser.email, name: currentUser.name, timeZone: currentUser.timeZone },
         attendees: [
-            { email: req.body.email, name: req.body.name }
+            { email: req.body.email, name: req.body.name, timeZone: req.body.timeZone }
         ]
     };
 
     // TODO: for now, first integration created; primary = obvious todo; ability to change primary.
     const result = await createEvent(currentUser.credentials[0], evt);
+
+    createConfirmBookedEmail(
+      evt
+    );
+
     res.status(200).json(result);
 }
diff --git a/yarn.lock b/yarn.lock
index 71a7ef8f..06f776fc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2201,6 +2201,11 @@ nodemailer@^6.4.16:
   resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.6.0.tgz#ed47bb572b48d9d0dca3913fdc156203f438f427"
   integrity sha512-ikSMDU1nZqpo2WUPE0wTTw/NGGImTkwpJKDIFPZT+YvvR9Sj+ze5wzu95JHkBMglQLoG2ITxU21WukCC/XsFkg==
 
+nodemailer@^6.6.1:
+  version "6.6.1"
+  resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.6.1.tgz#2a05fbf205b897d71bf43884167b5d4d3bd01b99"
+  integrity sha512-1xzFN3gqv+/qJ6YRyxBxfTYstLNt0FCtZaFRvf4Sg9wxNGWbwFmGXVpfSi6ThGK6aRxAo+KjHtYSW8NvCsNSAg==
+
 normalize-path@^3.0.0, normalize-path@~3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"