Mock Screenshot feature of SaaS applications
Tags: javascript, webdev, beginners, programming
Published: 2021-11-02 04:26:18

Well, while working on a SaaS application for a client recently, I came across a feature where the application allows you to take the screenshot of the entire page and have it sent in the email to the requested user either in the Image or PDF format. I got intrigued by this feature and thought of sharing it here.

<br>

STEP-1: __The setup__ -  We are going to need a backend which I have designed using Node.js and Express.js to take the screenshot and mail it along with the frontend. The [Backend](https://github.com/neer17/dev.to-screenshot-email-BE) and [Frontend](https://github.com/neer17/dev.to-screenshot-email-FE) can be found in the repos attached.

STEP-2: __The UI__ -  Honestly, the UI of a SasS application can be quite boring as it just displays a lot of boring data consists of metric cards, goal scorecards, charts etc. so to make this blog a little interesting the UI displays a collection of _Pokemon_ cards, because who doesn't like Pokemons, eh?
![UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yp0obox8ttrwe4ht01ue.png)

STEP-3: __The backend__ - Now since the UI is there, let's build the backend. It is gonna be quite simple. It just gonna have a route which would be called when the "Export Screenshot" button from the frontend is clicked. 

```js
const express = require("express")
let cors = require("cors")

const { sendEmail } = require("./helpers/helper")

const app = express()
app.use(cors())
app.use(express.json())

app.get("/", (req, res) => {
  res.send("backend home route")
})

app.post("/send-screenshot", async (req, res) => {
  const { receiversEmail } = req.body

  try {
    await sendEmail(receiversEmail)
    res.status(200).send("Email sent successfully")
  } catch (err) {
    res.status(400).send("Error in sending the email with screenshot")
  }
})

app.listen(4000, () => {
  console.info("Backend is running on port 4000")
})
```

STEP-4: __The Screenshot__ -  Now, since the frontend and backend are in place, let's use the Screenshot API's [query builder](https://www.screenshotapi.net/) to design a query for the screenshot.

Here, I have designed a query in order to get a high resolution, full page screenshot of the current page.

Following are the options that I am using explained:
- __Full page screenshot__ - it means the entire page including the scrollable area.
- __Retina__ - This would make the screenshot gets captured in high resolution
- __Lazy loading__ - this will make sure that all the content is loaded before the screenshot is taken
- __Fresh screenshot__ - The screenshot.api caches all the screenshots for you on their server so, using this just to make sure that I get a new screenshot every time. Alternatively, you can use "Destroy screenshot" feature which makes sure that your screenshot is not cached on their server in case the data displayed is sensitive.
- __Load event__ - A lot of nuances lies with this feature and using it correctly would save a lot of time. By default it is set to "load" but imagine a scenario where the page has a lot of images and obviously those images would take some time to load up, now since you want to make sure that all the images on the page show up in the screenshot therefore, we need to use the option "networkidle" here which essentially means that the API is going to wait until all the network calls are completed and then only it is going to take the screenshot.

Moreover, if you want to take screenshot of any public website that then following two options are going to be of utmost importance. "Block ads" and "no Cookie banners".

Finally, the query would look something like this
`https://shot.screenshotapi.net/screenshot?token=<YOUR_API_TOKEN>&url=<FORNTEND_URL>&full_page=true&fresh=true&output=image&file_type=jpeg&lazy_load=true&retina=true&wait_for_event=networkidle`

PS. For the frontend URL [ngrok](https://ngrok.com/download) can be used.

STEP-5: __The Email__ - We are gonna use [nodemailer](https://www.npmjs.com/package/nodemailer) for sending the screenshot. The _screenshot.api_ would send back the JSON response which would contain the `screenshot` key that would contain the URL of the screenshot. Now, for emailing the image we need to first fetch the image, write it to the disk using the `fs` module and then send it using `nodemailer`. Attaching the code to the below:

```js
const nodemailer = require("nodemailer")
const axios = require("axios")
const fs = require("fs")

const { SCREENSHOT_API_TOKEN } = require("./credentials")
const path = require("path")

const takeScreenshot = async () => {
  try {
    var query = "https://shot.screenshotapi.net/screenshot"
    let url = "<FRONTEND_URL>"
    query += `?token=${SCREENSHOT_API_TOKEN}&url=${url}&full_page=true&fresh=true&output=image&file_type=jpeg&lazy_load=true&retina=true&wait_for_event=networkidle`
    const response = await axios.get(query)

    console.info(JSON.stringify(response.data))

    const imageStream = await axios.get(screenshotURL, {
      responseType: "stream",
    })
    return imageStream
  } catch (err) {
    console.error("\nError while taking the screenshot", err)
    throw err
  }
}

const sendEmail = async (receiversEmail) => {
  try {
    let mailerConfig = {
      host: "smtp.gmail.com",
      port: 587,
      secure: false, // true for 465, false for other ports
      auth: {
        user: "<GMAIL_ID>", // user
        pass: "<APP_PASSWORD>", // password
      },
    }

    let transporter = nodemailer.createTransport(mailerConfig)

    const imageStream = await takeScreenshot()

    const imagePath = path.join(__dirname, "..", "output", "screenshot.png")
    imageStream.data
      .pipe(fs.createWriteStream(imagePath))
      .on("finish", () => {
        // send mail with defined transport object
        let info = await transporter.sendMail({
          from: "<SENDER'S EMAIL ADDRESS>", // sender address
          to: `${receiversEmail}`, // list of receivers
          subject: "Screenshot requested", // Subject line,
          attachment: [
            {
              filename: imagePath,
              content: imageBuffer,
              encoding: "base64",
            },
          ],
          text: "Hello! find the screenshot that you requested attached", // plain text body
          html: "<b>Hello! find the screenshot that you requested attached</b>", // html body
        })
      })
      .on("error", (err) => {
        console.error("Stream closed with following error: ", err)
      })
    return true
  } catch (err) {
    console.error("\nError in sending the email", err)
    throw err
  }
}

module.exports = {
  sendEmail,
}
```
PS. If you want to use your Gmail account then you need to generate an app password. More details can be found [here](https://nodemailer.com/usage/using-gmail/)

<br>
❤ Like this blog post? Do follow me on [Twitter](https://twitter.com/neeraj_sewani)

Do checkout my other blog posts:
- [Opinionated Guide On Tweaking VS Code For Productivity](https://dev.to/neer17/opinionated-guide-on-tweaking-vs-code-for-productivity-1o53)
- [Make A Twitter Bot In Node.js](https://dev.to/neer17/twitter-bot-in-node-js-329)