---
# System prepended metadata

title: 'Got tired of logging into Listmonk repeatedly to check the newsletter, so I implemented a preview myself'

---

# Avoid logging into Listmonk for Newsletter Quality Check. Use this hack instead

How I Added Discord Preview Feature to Our Listmonk? How we schedule weekly emails ? How to receive email Preview in the Discord server? 

## The Hurdle

We have set up a system of doing a **mini-research** every week. Each one of the team members takes up a mini-unknown area.

We learn about it, create a **solution**, and if it's good then transform it into a blog so that others may **benefit** from it.

We wanted to keep the data with us. So, We set up a **[Blogging platform](https://journal.hexmos.com/)** using **[Ghost Blog ](https://ghost.org/)**.

As the quality of the content got a little better people started recognizing and subscribing to our blog.

Once we had 50+ subscribers it was evident that we needed to set up a newsletter  system.

Ghost came with a newsletter setup, but we had to pay for **[MailChimp](https://mailchimp.com/)** to use that, to avoid that we set up a self-hosted  Email manager **[Listmonk](https://listmonk.app/)** on our own.

we had to spend a week coming up with a sync system for migrating subscribers.

We set up a sync system and migrated all the Ghost subscribers to Listmonk subscribers so that we can make an automated scheduled newsletter every week.


## Scheduling Newsletter

We wanted a scheduled newsletter for every Monday, as the writing would be finished by Sunday.

We thought of making it automated so it does not depend on manually sending it and made use of a **[Cron job](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/)** for automation.

We have one more newsletter called 365 Reasons which will be sent throughout the year. 

(banner)

The script is fully automated, but we prefer to do a quality check before a campaign sends emails to dozens of recipients.

We should not send spam or corrupted emails, it is wasting a person's time. Logging in every time would consume our **precious time**.

Since this is a daily chore, we wanted to make it easy on ourselves

Listmonk didn't have an integrated solution to this, so we came up with our idea i.e. Sending a snapshot of the HTML before sending the newsletter.

## How I Implemented a Preview

We use a **[Jinja](https://jinja.palletsprojects.com/en/3.1.x/)** template for rendering the post, we fetch the last week's posts from our blog. Render the post using a template.

In the end, we have an HTML Posted to the Listmonk server. We fetch the scheduled Email and take a Screenshot of it.

I used **[Pyppeteer](https://pypi.org/project/pyppeteer/)** library to get a screenshot of the HTML before sending it.

### Getting Preview

I have a function that takes the **[Campaign ID](https://listmonk.app/docs/apis/campaigns/)** as input and sends a screenshot to the Discord server. 

In `send_discord_preview_notification` function, it gets an HTML response and and inputted into `send_discord_notification`function.

If there is an error in getting a response then the `log_message` function will send an error message to our Discord server.

```python
LIST_MONK_URL = lm.example.com # Give Your LM Url 
def send_discord_preview_notification(id):  # This will send Screenshot of HTML file to discord server
    url = f"{LIST_MONK_URL}/api/campaigns/{id}/preview"
    try:
        response = requests.get(url, headers=lm_headers)
        response.raise_for_status()  # This will raise an HTTP error if the response was an HTTP error
    except requests.exceptions.RequestException as e:
        send_discord_notification(f"An error occurred: {e}")
    else:
        if response.status_code == 200:
            send_screenshot_file(response)  # This will initiate Screenshot sending feature
        else:
            log_message(f"Failed to fetch data. Status code: {response.status_code}")
```

### Temporary File Handling
The `send_screenshot_file` function takes the response as input and sends a screenshot to discord server.

I used the **[Temporary directory](https://docs.python.org/3/library/tempfile.html)** for storing the temp file generated while taking a Screenshot of the campaign.

The response is written inside `temp_file_path` in .html format in the temporary directory.

Initiate **[Asynchronous operation](https://docs.python.org/3/library/asyncio.html)** to take a screenshot of temp.html file by `screenshot` function and save a screenshot in `screenshot_path`.

The Screenshot saved in `screenshot_path` is posted to the discord server.
```python
import tempfile
WEBHOOK_CHANNEL_URL = "https://discord.com/api/webhooks/121fwebwefuik9886/Zffwuica" 
# Give Your Webhook 
def send_screenshot_file(response):
    with tempfile.TemporaryDirectory() as tmpdirname:
        temp_file_path = f"{tmpdirname}/temp.html"
        with open(temp_file_path, "wb") as temp_file:
            temp_file.write(response.encode()) # Directly encode the string
        screenshot_path = f"{tmpdirname}/Screenshot.png"
        asyncio.run(screenshot(screenshot_path, f"file://{temp_file_path}"))
        with open(screenshot_path, "rb") as img:
            response = requests.post(WEBHOOK_CHANNEL_URL, files={"file": img})
```
### Screenshot
I tried many other solutions but it didn't go well some of them were sending previews before images were loaded and others only dent half of the preview.

Finally, came up with using  `Pyppeteer` library. This Library didn't give any problems in the preview.

This function will load HTML in the browser take a screenshot of it and save it in **`screenshot_path`**.

```python
import asyncio
from pyppeteer import launch
async def screenshot(screenshot_path, temp_file_path):
    browser = await launch()
    page = await browser.newPage()
    await page.goto(temp_file_path)
    await page.screenshot({"path": screenshot_path, "fullPage": True})
    await browser.close()
```
<img src="https://hackmd.io/_uploads/r1cnt_rlA.png" width="50%">
<img src="https://hackmd.io/_uploads/BJ_kodSeR.png" width="50%">

## Conclusion

My solution s
My Preview feature reduced the time for checking scheduled Emails. Using it our team also can give a review for new production Emails.

