Use Playwright to visit each URL and save a screenshot, record successes and failures in a manifest, then use Nodemailer to email the manifest and captured images through an SMTP service. The example below processes a text file of URLs sequentially, keeps going when an individual page fails, and attaches each successful screenshot. For a polished or very large report, package the images separately or create a PDF/HTML report and send an authorized download link instead.
Choose what the email should contain
For a modest batch, attach the screenshots and include a per-URL status list in the email. This is easy for recipients to inspect but can produce a large message. A ZIP plus an index is more convenient when there are many images; a PDF or HTML report can put each URL, status, and image together in one document. The Playwright and Nodemailer documentation establishes the capture and attachment mechanisms, not a required report format or a universal email-size limit.
- Attachments plus a status list: straightforward and useful when the image count and total size are manageable.
- ZIP plus an index: keeps a batch together and gives recipients a separate record of failed URLs.
- PDF or HTML report: better when the recipient needs a browsable or printable document, but requires an additional report-generation step.
- Download link: use an authorized file-storage service if the deliverable is too large for the mail provider’s current limits.
The runnable example uses attachments plus a plain-text status list, avoiding an extra packaging dependency.
Install the tools and prepare the inputs
This implementation uses Node.js, Playwright’s Chromium browser, and Nodemailer. Check the current installation guidance and requirements in the Playwright documentation and Nodemailer documentation before deploying it; browser and package requirements can change.
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
- CRISP CLARITY: This 23.8″ Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
- INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
- THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors
- WORK SEAMLESSLY: This sleek monitor is virtually bezel-free on three sides, so the screen looks even bigger for the viewer. This minimalistic design also allows for seamless multi-monitor setups that enhance your workflow and boost productivity
- A BETTER READING EXPERIENCE: For busy office workers, EasyRead mode provides a more paper-like experience for when viewing lengthy documents
- Create a project directory and initialize Node.js with
npm init -y. - Install the dependencies:
npm install playwright nodemailer. - Install Playwright’s Chromium browser:
npx playwright install chromium. - Create
urls.txtwith one HTTP or HTTPS URL per line. Blank lines and lines beginning with#are ignored. - Set SMTP connection details and the destination address in environment variables as described below. Keep credentials out of the source file and do not commit them to version control.
Example urls.txt:
https://example.com
https://www.wikipedia.org
# Lines beginning with # are skipped
Capture the URLs and email the report
Save the following as bulk-report.mjs. It reuses one browser, opens a fresh page for each URL, captures a full-page PNG, and continues after navigation or screenshot errors. Each result is recorded with its original URL, timestamp, filename when successful, and status. A page that reaches a site but presents a login screen, bot check, or other restricted content may still be captured; browser automation cannot guarantee access to arbitrary sites.
import fs from 'node:fs/promises';
import path from 'node:path';
import { chromium } from 'playwright';
import nodemailer from 'nodemailer';
const inputFile = process.env.URLS_FILE ?? 'urls.txt';
const outputDir = process.env.OUTPUT_DIR ?? 'screenshots';
const timeoutMs = Number(process.env.PAGE_TIMEOUT_MS ?? 30000);
const emailTo = process.env.EMAIL_TO;
function safeName(value, index) {
let host = 'page';
try {
host = new URL(value).hostname || 'page';
} catch {}
const base = host.replace(/[^a-z0-9.-]+/gi, '_').replace(/^.+|.+$/g, '') || 'page';
return `${String(index + 1).padStart(3, '0')}-${base}.png`;
}
function validateUrl(value) {
try {
const parsed = new URL(value);
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
} catch {
return false;
}
}
if (!emailTo) throw new Error('Set EMAIL_TO to the report recipient address.');
const raw = await fs.readFile(inputFile, 'utf8');
const urls = raw.split(/r?n/).map(line => line.trim())
.filter(line => line && !line.startsWith('#'));
if (urls.length === 0) throw new Error(`No URLs found in ${inputFile}.`);
await fs.mkdir(outputDir, { recursive: true });
const results = [];
const browser = await chromium.launch({ headless: true });
try {
for (const [index, url] of urls.entries()) {
const capturedAt = new Date().toISOString();
if (!validateUrl(url)) {
results.push({ url, capturedAt, status: 'error', error: 'Only valid HTTP and HTTPS URLs are accepted.' });
continue;
}
const filename = safeName(url, index);
const filePath = path.join(outputDir, filename);
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
try {
const response = await page.goto(url, { waitUntil: 'load', timeout: timeoutMs });
await page.screenshot({ path: filePath, fullPage: true, type: 'png' });
results.push({
url,
capturedAt,
status: 'success',
httpStatus: response?.status() ?? null,
filename
});
} catch (error) {
results.push({ url, capturedAt, status: 'error', error: String(error.message ?? error) });
await fs.rm(filePath, { force: true });
} finally {
await page.close();
}
}
} finally {
await browser.close();
}
const report = results.map(item => {
if (item.status === 'success') {
return `SUCCESS | ${item.url} | HTTP ${item.httpStatus ?? 'status unavailable'} | ${item.filename} | ${item.capturedAt}`;
}
return `FAILED | ${item.url} | ${item.error} | ${item.capturedAt}`;
}).join('n');
await fs.writeFile(path.join(outputDir, 'report.txt'), report + 'n', 'utf8');
const port = Number(process.env.SMTP_PORT ?? 587);
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port,
secure: process.env.SMTP_SECURE === 'true',
auth: process.env.SMTP_USER ? {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
} : undefined
});
const attachments = results
.filter(item => item.status === 'success')
.map(item => ({ filename: item.filename, path: path.join(outputDir, item.filename) }));
attachments.push({ filename: 'report.txt', path: path.join(outputDir, 'report.txt') });
await transporter.sendMail({
from: process.env.EMAIL_FROM ?? process.env.SMTP_USER,
to: emailTo,
subject: `Bulk screenshot report: ${results.length} URLs`,
text: `Capture results (${results.filter(item => item.status === 'success').length} successful, ${results.filter(item => item.status === 'error').length} failed):nn${report}`,
attachments
});
console.log(`Email sent to ${emailTo}. ${attachments.length - 1} screenshot(s) attached.`);
The script records a successful capture when navigation and screenshot saving complete; an HTTP error status alone does not necessarily mean navigation failed. Review the reported status and image if the target site’s response matters to your workflow. The capturedAt value is taken immediately before navigation, so it marks the start of that URL’s attempt.
Set SMTP and recipient variables
Configure the SMTP host, port, recipient, sender, and credentials required by your mail provider. For example, on a POSIX shell, export values before running the script:
Rank #2
- CRISP CLARITY: This 22 inch class (21.5″ viewable) Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
- 100HZ FAST REFRESH RATE: 100Hz brings your favorite movies and video games to life. Stream, binge, and play effortlessly
- SMOOTH ACTION WITH ADAPTIVE-SYNC: Adaptive-Sync technology ensures fluid action sequences and rapid response time. Every frame will be rendered smoothly with crystal clarity and without stutter
- INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
- THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors
export SMTP_HOST="smtp.example.net"
export SMTP_PORT="587"
export SMTP_SECURE="false"
export SMTP_USER="your-smtp-user"
export SMTP_PASS="your-smtp-password"
export EMAIL_FROM="reports@example.net"
export EMAIL_TO="recipient@example.net"
node bulk-report.mjs
For port 465, set SMTP_SECURE="true" when that matches your provider’s configuration. SMTP providers differ in authentication, sender authorization, relay policy, and message-size limits; use that provider’s current requirements rather than assuming these example values apply universally. Nodemailer documents SMTP transport configuration at https://nodemailer.com/smtp.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsAdapt the capture and reporting behavior
Viewport or full-page images
The example uses fullPage: true, which captures the scrollable page and can result in tall, large files. To capture only the initial viewport, change it to fullPage: false or omit the option. For a particular element, use a Playwright locator’s screenshot method instead of a page screenshot. The screenshot guide covers full-page capture, element capture, and returning image bytes: https://playwright.dev/docs/screenshots.
Wait for content that loads late
waitUntil: 'load' waits for the page’s load event, but some sites render content afterward or load images lazily. For those pages, use a site-specific selector wait, a carefully chosen delay, or another readiness condition before calling page.screenshot. A fixed delay adds time to every affected capture and does not guarantee that dynamic content is ready; prefer an observable page condition where possible.
Rank #3
- Clear visuals. Fluid motion: A 144Hz refresh rate and 1ms MPRT deliver smooth, tear‑free motion across work, gaming, and streaming for clearer, more fluid viewing.
- Eye comfort: TÜV Rheinland 3‑star* certification reduces harmful blue light while preserving stunning color quality without compromise. *TÜV Rheinland 3-star eye comfort certification.
- Wide viewing angle: Get consistent views across a wide 178° /178° viewing angle.
- In-Plane Switching (IPS): See excellent color accuracy and consistency across wide viewing angles with In-plane Switching (IPS) technology.
- Ultra-thin bezels: Maximize your viewing experience with thin bezels.
Control batch size and concurrency
The sample deliberately processes URLs sequentially. It avoids creating a large number of browser pages at once and makes per-URL results easy to associate, but a large batch will take longer. If you add concurrency, bound the number of simultaneous pages and test against your machine and target sites. There is no universal safe concurrency level or throughput guarantee in the cited documentation. Consider target-site rate limits and terms, and avoid sending traffic the site has not authorized.
Make a richer report
The generated report.txt gives recipients an explicit result for every input, including invalid URLs and failed loads. To make an HTML report, generate escaped HTML from the same results array and either embed images as data or distribute the HTML and image files together. To make a PDF, add a PDF-generation step after capturing. For a large batch, a ZIP containing the images and report can be more manageable than attaching each PNG separately.
Recommended Free Tools
Reliability, privacy, and cost considerations
- Failures are expected: timeouts, browser errors, blocked access, authentication walls, and dynamic pages can prevent a useful screenshot. The script records an error for an individual URL rather than dropping it, but it does not retry automatically.
- Retries need care: a retry can help with transient network failures, but can also duplicate requests or prolong a batch. If adding retries, cap attempts, record each outcome, and avoid retrying access-denied responses blindly.
- Email size is provider-specific: full-page images can be much larger than viewport captures. Nodemailer can use paths and streams for attachments rather than requiring every file to be loaded into memory; this does not override the SMTP provider’s message-size policy. See Nodemailer attachment documentation.
- Protect credentials and captured data: use environment-based secret management appropriate to the deployment, restrict access to the output directory, and delete screenshots when no longer needed. Screenshots can contain personal or confidential page content.
- Verify delivery operationally: a resolved
sendMailcall means the SMTP server accepted the message for processing, not that every recipient’s inbox received it. Handle errors from SMTP and monitor delivery through the mail provider’s tools.
Troubleshooting
Playwright says the browser executable is missing
Install the browser build that matches the installed Playwright package with npx playwright install chromium. In constrained deployment environments, follow Playwright’s current installation guidance for required operating-system dependencies.
Rank #4
- CURVED FOR ENHANCED ENGAGEMENT: An immersive viewing experience with a curved monitor that wraps more closely around your field of vision; It creates a wider view, enhancing depth perception and minimizing peripheral distraction
- SMOOTH PERFORMANCE FOR SEAMLESS CONTENT: Stay in the action when playing games, watching videos, or working on creative projects; The 100Hz refresh rate reduces lag and motion blur so you don't miss a thing in fast-paced moments¹
- MORE GAMING POWER: Gain the edge with optimizable game settings; Color and image contrast can be adjusted to see scenes more vividly and spot enemies hiding in the dark; Game Mode adjusts any game to fill the screen so you can view every detail²
- KEEP IT EASY ON THE EYES: Care for your eyes and stay comfortable, even during long sessions; Advanced eye comfort technology certified by TÜV reduces eye strain by minimizing blue light and reducing irritating screen flicker²
- INCREASED VERSATILITY: Connect to more; Plug devices straight into your monitor for increased flexibility, making your computing environment even more convenient
A URL is marked failed after waiting too long
The target may be slow, unreachable, or holding navigation open. Increase PAGE_TIMEOUT_MS for the run if a longer wait is acceptable, and verify that the URL is reachable from the machine running the script. A longer timeout increases the worst-case duration of a batch.
The image is blank, incomplete, or shows a challenge
Check the page manually from the same network and environment. Content may require authentication, client-side rendering, a consent interaction, or access that the site blocks for automated browsers. Add a targeted wait for a known element if rendering is merely delayed; do not treat a challenge or restricted page as a successful capture of the intended content.
SMTP authentication or connection fails
Confirm SMTP_HOST, SMTP_PORT, the SMTP_SECURE setting, credentials, and the provider’s permitted authentication method. Check whether outbound SMTP is blocked in the runtime environment and whether the sender address is authorized. The relevant provider’s current SMTP documentation is authoritative for its requirements.
Best Value
- 【INTEGRATED SPEAKERS】Whether you're at work or in the midst of an intense gaming session, our built-in speakers provide rich and seamless audio, all while keeping your desk clutter-free.
- 【EASY ON THE EYES】 Protect your eyes and enhance your comfort with Blue-Light Shift technology. This feature reduces harmful blue light emissions from your screen, helping to alleviate eye strain during long hours of use and promoting healthier viewing habits.
- 【WIDEN YOUR PERSPECTIVE】Our sleek minimal bezel design ensures undivided attention. The nearly bezel-free display seamlessly connects in a dual monitor arrangement, delivering an unobstructed view that lets you focus on more at once, completely distraction-free.
The email is rejected or has no attachments
Check the SMTP error and the provider’s size limit, confirm that at least one URL captured successfully, and inspect the output directory and generated report.txt. If the total message is too large, reduce capture scope or send a link to an authorized stored report rather than repeatedly resending oversized attachments.
Or skip the browser setup
ScreenshotNeo is a screenshot API and MCP server: a GET request with a URL returns an image or PDF, and its MCP tools let AI agents take screenshots. Its clean-shot steps accept cookie/consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status. Those behaviors can simplify a capture workflow, but this one-call example captures one URL per request; your batch still needs to iterate over its input and assemble and email the results.
See the ScreenshotNeo API documentation for request options. This cURL example saves a WebP image for one URL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
ScreenshotNeo has an MCP server for Claude, Cursor, and any MCP client. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for ScreenshotNeo free: 1,000 screenshots a month, no card required.
Sources
- Playwright Page API
- Playwright screenshots guide
- Nodemailer
- Nodemailer SMTP transport
- Nodemailer attachments
Frequently Asked Questions
Does the script capture every URL successfully?
No. Individual sites may be unavailable, slow, restricted, or rendered differently in automation. The script reports failures and cannot guarantee access or a useful image for arbitrary pages.
Can I run this as a scheduled report?
Yes. Run it from a scheduler in an environment with the required Node.js dependencies, Chromium installation, network access, and SMTP configuration; check the scheduler’s logs and mail-provider delivery records for failures.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

