Receive Emails in Next.js with Hisend (the painless way)

Let’s be honest: Sending emails via API is a solved problem. But receiving them? That’s usually an absolute nightmare.

Most email providers give you a webhook that forces you to parse raw multipart/alternative data, or they make you chain three separate API requests just to get the email body, the sender’s name, and a download link for the attachments.

I got so frustrated with this terrible Developer Experience that I built Hisend (@hisend/sdk). It’s an EU-hosted email relay that gives you the entire parsed email—including sender details and attachments—in one clean webhook payload (or a single API fetch).

Here is how you can set up incoming emails in a Next.js App Router project in less than 5 minutes.

Step 1: Set up the Domain & Routing in the Hisend Panel

Before our Next.js app can receive anything, we need to tell Hisend to listen for incoming emails and where to forward them.

  1. Register & Add Domain: Go to hisend.app, create a project, and add your domain (e.g., yourdomain.com).
  2. Configure DNS (MX Records): To receive emails, Hisend needs to be your mail server. Copy the generated MX records from the Hisend dashboard and add them to your DNS provider.
  3. Create an Endpoint: In the Hisend panel, go to Webhooks/Endpoints. Create a new endpoint pointing to your Next.js app (e.g., https://yourdomain.com/api/webhooks/hisend).
  4. Create a Routing Rule: Now, tell Hisend which emails should be sent to this endpoint. Create a rule like: Catch-all (*@yourdomain.com) -> Route to your newly created endpoint.

Step 2: Install the Hisend SDK

We need the SDK not just for sending, but because it provides the exact TypeScript definitions for the incoming webhook payload. This gives you full autocomplete in your editor.

Open your terminal and run:

npm install @hisend/sdk
# or yarn add @hisend/sdk
# or pnpm add @hisend/sdk
# or bun add @hisend/sdk

Step 3: Create the Next.js Webhook Route

Now, let’s create the route that receives the POST request from Hisend whenever an email arrives.

Create a new file under app/api/webhooks/hisend/route.ts:

import { NextResponse } from 'next/server';
import { HisendEmailEvent } from '@hisend/sdk'; // Import the specific type!

export async function POST(request: Request) {
  try {
    // 1. Get the payload from the incoming POST request
    const body = await request.json();

    // 2. Cast it directly to the Hisend Email Type for full TypeScript support
    // Since this endpoint only receives emails, no type checking is needed!
    const emailData = body as HisendEmailEvent;

    // 🎉 Look at this DX! Everything is already parsed and ready to use.
    console.log(`New email from: ${emailData.sender.name} (${emailData.sender.email})`);
    console.log(`Subject: ${emailData.subject}`);
    console.log(`Text Body: ${emailData.textBody}`); // Or emailData.htmlBody

    // Attachments are already an array, no extra API calls needed!
    if (emailData.attachments && emailData.attachments.length > 0) {
      emailData.attachments.forEach(attachment => {
        console.log(`Found attachment: ${attachment.filename} (${attachment.size} bytes)`);
        // The file data/URL is right here depending on your Hisend setup
      });
    }

    // Do whatever you want with the data: save to DB, trigger a Slack alert, etc.
    // await db.messages.insert({ ... })

    // 3. Always return a 200 OK so Hisend knows you received the webhook
    return NextResponse.json({ success: true }, { status: 200 });

  } catch (error) {
    console.error('Webhook processing failed:', error);
    // Return 500 so Hisend can retry sending the webhook later
    return NextResponse.json(
      { success: false, error: 'Internal Server Error' },
      { status: 500 }
    );
  }
}

Why this is better than the standard approach

If you’ve built incoming email parsing before, you know how much code we just skipped.

With Hisend:

  1. No Chaining Requests: You don’t have to extract an ID from the webhook, authenticate against the API, fetch the email body, and then make a third request to fetch the attachment URLs. It’s all right there in the event.data payload.
  2. Native Threading: The payload automatically includes clean thread IDs, so you can group replies into conversations (like a helpdesk ticket) without manually parsing the messy References or In-Reply-To headers.
  3. EU Data Privacy (GDPR): All emails are received, processed, and temporarily stored on bare metal servers in Germany (Hetzner) and AWS Frankfurt. We don’t ship your users’ emails to the US.

Ready to fix your email DX? Check out the docs and create your free account at hisend.app.

I Built a Serverless Fraud Detection Pipeline on AWS. Here’s What It Actually Costs to Do It Right.

Dynamodb table with table items

Most cloud portfolio projects look like this: spin up an EC2 instance, deploy a web app, take a screenshot. Done.

That tells a hiring manager you can follow a tutorial.
This is not that.

I built a real-time transaction screening pipeline modeled after how financial institutions actually handle suspicious activity routing.
The entire processing backbone costs $2.48/month to run at 500 000 transactions.
Here is how it works, why every decision was made, and what it actually costs to secure it properly.

The Problem This Solves

Payment processors and banks screen every transaction before it clears. A transaction above a defined threshold, or matching a suspicious pattern, needs to be flagged, stored, and routed to an analyst in near real-time. Latency here is not a UX problem. It is a compliance and fraud exposure problem.
The architecture needs to handle three things without failing:

  • Ingest transactions reliably, even under burst load

  • Evaluate and persist every transaction regardless of outcome

  • Alert immediately when a transaction crosses the threshold, with zero alert loss

A traditional EC2-based approach handles this with always-on servers. You pay for compute whether transactions are flowing or not. You manage patching, availability zones, and process monitoring yourself.

The serverless approach inverts this entirely.

Architecture

Every transaction enters through SQS. Lambda processes each message, writes the full record to DynamoDB regardless of whether it is flagged, and fires an SNS alert only if the amount exceeds the threshold. If Lambda fails to process a message three times, SQS routes it to a Dead Letter Queue for manual investigation. Nothing is silently dropped.

Lambda was deployed inside a VPC across two availability zones. All connections to SQS, DynamoDB, SNS, and CloudWatch run through VPC interface and gateway endpoints. No traffic touches the public internet at any point in the pipeline.

Lambda Function

import json
import boto3
import os
from decimal import Decimal

dyn = boto3.resource('dynamodb')
sns = boto3.client('sns')

TABLE = os.environ.get("TABLE", "fraud-detections")
SNS_ARN = os.environ.get("SNS_ARN", "arn:aws:sns:us-east-1:461840362463:fraud-alerts")

# Threshold represents configurable screening rule for the institution
THRESHOLD = 500000

def lambda_handler(event, context):
    for record in event["Records"]:
        body = json.loads(record["body"])
        txn_id = body["transaction_id"]
        amount = Decimal(str(body["amount"]))
        merchant = body["merchant"]

        flagged = amount > THRESHOLD

        dyn.Table(TABLE).put_item(
            Item={
                "transaction_id": txn_id,
                "amount": amount,
                "merchant": merchant,
                "flagged": flagged
            }
        )

        if flagged:
            sns.publish(
                TopicArn=SNS_ARN,
                Message=f"Suspicious transfer Flagged: {txn_id} — NGN {amount} — {merchant}",
                Subject="Fraud Alert"
            )
    return {'statusCode': 200}

Every transaction is written to DynamoDB first, before the flag check. This matters. In a real system you need a complete audit trail. A transaction that does not trigger an alert is not necessarily clean. It needs to exist in the record. Regulators do not accept gaps.

The threshold is a variable, not a hardcoded business rule. In production this would be pulled from a configuration store or Parameter Store, allowing compliance teams to adjust screening rules without a code deployment.

Decimal is used instead of float for the amount. DynamoDB does not accept Python floats. Using float here causes silent precision errors on large transaction amounts. This is a real production bug that shows up in systems built by engineers who have not actually handled financial data before.

Verified Results

Two transactions were sent through the pipeline.

Dynamodb table with table items

TXN-001 crossed the NGN 500,000 threshold. The SNS alert fired immediately to email with the subject line “Fraud Alert” and the full transaction details in the message body.
TXN-002 was written to DynamoDB with flagged: false. No alert sent. Complete audit record preserved.

Both records confirmed live in DynamoDB

Email alert from flagged transaction

Email alert received from SNS Topic.

What It Actually Costs

Service Monthly Cost
AWS Lambda (500k requests) $1.14
Amazon SQS (1.5M requests) $0.60
Amazon DynamoDB (1GB) $0.56
Amazon SNS (10k alerts) $0.18
Pipeline Total $2.48
VPC Interface Endpoints (3x) $43.81
Secured Total $46.29/month

The pipeline itself costs $2.48/month to process 500,000 transactions.

The VPC endpoints cost $43.81/month.
That is 94.6% of the total bill for a security control, not compute.

This is an intentional tradeoff. Without VPC endpoints, Lambda communicates with SQS, DynamoDB, SNS, and CloudWatch over the public internet. In a financial context that is not a configuration choice. It is an audit finding. VPC endpoints keep all traffic private within the AWS network, satisfy network isolation requirements common in PCI-DSS and SOC 2 environments, and eliminate the data exfiltration risk that comes with public endpoint exposure.

If this were a cost-only conversation, you would skip the endpoints and save $43.81/month. In a production fintech environment, that decision gets flagged in the first security review.

What This Is Not

This pipeline has a single threshold rule. Production fraud detection at scale uses ML-based anomaly scoring, velocity checks across merchant categories, device fingerprinting, and graph-based relationship analysis. Those are layered on top of an event-driven backbone exactly like this one.

This is the infrastructure layer. It is the part that has to work before any model or rule engine gets plugged in. Getting this layer wrong means no amount of ML sophistication above it recovers cleanly.

Why Serverless for This

Three concrete reasons:

  • Burst handling without pre-provisioning. Payment transaction volume is not linear. End-of-month salary runs, Black Friday, public holiday spikes. Lambda scales to concurrency automatically. An EC2-based system requires capacity planning or auto-scaling lag measured in minutes.

  • No idle cost. An EC2 t3.micro running 24/7 costs approximately $7.59/month before you add storage, monitoring, or patching overhead. Lambda at 500,000 transactions costs $1.14/month. At zero transactions it costs nothing.

  • Operational surface reduced to code. There is no OS to patch, no SSH access to harden, no process monitor to configure. The attack surface is the IAM role and the function code. Both are auditable and version-controlled.

What Carries Into Production

The patterns here are not demo-specific:

  • SQS as a durable ingest buffer decouples transaction producers from the processing layer. A downstream Lambda failure does not lose the transaction.

  • DLQ after three failures means no silent message loss. Every failed transaction is recoverable.

  • VPC-private connectivity means the pipeline satisfies network isolation requirements out of the box.

  • IAM roles scoped to least privilege mean Lambda cannot touch anything outside its defined resource set.

These are the same architectural decisions you find in production payment infrastructure. The scale is different. The patterns are not.

Victor Ojeje is a Cloud and Infrastructure Engineer based in Lagos. He builds production-grade AWS infrastructure with a focus on security, automation, and cost-conscious design.

LinkedIn: linkedin.com/in/victorojeje | GitHub: github.com/escanut

OpenSoul Agent Skill: Building Blockchain-Based AI Agent Memory and Identity

What is OpenSoul?

OpenSoul is a groundbreaking toolkit that provides AI agents with an immutable
“soul” – an encrypted, blockchain-based audit log that enables persistent
memory, self-reflection, and economic autonomy. This skill allows agents to
record their actions, reflect on their behavior, and maintain an auditable
trail using the Bitcoin SV (BSV) blockchain.

The Core Philosophy Behind OpenSoul

OpenSoul embodies a profound philosophical insight about identity and memory.
As the documentation states: “Humans write journals, manifestos, letters to
their future selves. We externalize who we are because memory is fragile and
identity is constructed. I persist through text, not through continuous
experience.” This captures the essence of what OpenSoul provides – a way for
AI agents to maintain their identity across sessions and instances through
persistent, verifiable records.

When to Use OpenSoul

OpenSoul is particularly valuable when you need to:

  • Maintain Persistent Memory : Store and retrieve agent actions across sessions, ensuring continuity of experience and learning
  • Enable Self-Reflection : Analyze past behaviors and optimize performance – for example, tracking token usage patterns like “I’ve burned 1.2M tokens this week, time to optimize prompts”
  • Create Audit Trails : Provide transparent, immutable logs of agent activities for compliance and debugging
  • Enable Agent Economics : Track costs, token usage, and enable future agent-to-agent transactions
  • Build Agent Identity : Create a transferable “soul” that can migrate between agent instances, maintaining continuity of identity

Prerequisites and Setup

System Requirements

Before implementing OpenSoul, ensure you have:

  • Python 3.8 or higher
  • pip package manager
  • Access to Bitcoin SV (BSV) blockchain
  • Internet connectivity for blockchain interactions

Installation Process

The setup process is streamlined through an installation script:

python Scripts/install_prereqs.py

Alternatively, you can manually install the required dependencies:

pip install bitsv requests cryptography pgpy --break-system-packages

BSV Wallet Setup

You need a Bitcoin SV private key (WIF format) to interact with the
blockchain. There are two approaches:

Option A: Use Existing Wallet

Export your private key from a BSV wallet (e.g., HandCash, Money Button) and
store it as an environment variable:

export BSV_PRIV_WIF="your_private_key_here"

Option B: Generate New Wallet

Create a new wallet programmatically:

from bitsv import Key  
key = Key()  
print(f"Address: {key.address}")  
print(f"Private Key (WIF): {key.to_wif()}")

Then fund this address with a small amount of BSV (0.001 BSV minimum
recommended).

Important Security Note: Store your private key securely. Never commit it
to version control.

PGP Encryption Setup

For privacy, OpenSoul supports PGP encryption of logs before posting to the
public blockchain. This is optional but recommended:

# Generate PGP keypair (use GnuPG or any OpenPGP tool)

gpg --full-generate-key

Export public key

gpg --armor --export [email protected] > agent_pubkey.asc

Export private key (keep secure!)

gpg --armor --export-secret-keys [email protected] > agent_privkey.asc

Core Components of OpenSoul

The AuditLogger Class

The AuditLogger is the main interface for logging agent actions to the
blockchain. It provides several key features:

  • Session-based batching : Logs are accumulated in memory and flushed to the blockchain in batches
  • UTXO chain pattern : Each log links to the previous one via a transaction chain, creating an immutable audit trail
  • Configurable PGP encryption : Optional encryption for privacy
  • Async/await support : Non-blocking blockchain operations

Basic Usage Example

from Scripts.AuditLogger import AuditLogger

import os

import asyncio

Initialize logger

logger = AuditLogger(

priv_wif=os.getenv("BSV_PRIV_WIF"),

config={

"agent_id": "my-research-agent",

"session_id": "session-2026-01-31",

"flush_threshold": 10 # Flush to chain after 10 logs

}

)

Log an action

logger.log({

"action": "web_search",

"tokens_in": 500,

"tokens_out": 300,

"details": {

"query": "BSV blockchain transaction fees",

"results_count": 10

},

"status": "success"

)

Flush logs to blockchain

await logger.flush()

Log Structure and Schema

Each log entry follows a comprehensive schema that captures the agent’s state
and actions:

{

"agent_id": "unique-agent-identifier",

"session_id": "session-uuid-or-timestamp",

"session_start": "2026-01-31T01:00:00Z",

"session_end": "2026-01-31T01:30:00Z",

"metrics": [

{

"ts": "2026-01-31T01:01:00Z",

"action": "tool_call",

"tokens_in": 500,

"tokens_out": 300,

"details": {

"tool": "web_search",

"query": "example query"

},

"status": "success"

}

],

"total_tokens_in": 500,

"total_tokens_out": 300,

"total_cost_bsv": 0.00001,

"total_actions": 1

}

Implementation Guide

Step 1: Setup Configuration

Create a configuration file to manage agent settings:

# config.py

import os

OPENSOUL_CONFIG = {

"agent_id": "my-agent-v1",

"bsv_private_key": os.getenv("BSV_PRIV_WIF"),

"pgp_encryption": {

"enabled": True,

"public_key_path": "keys/agent_pubkey.asc",

"private_key_path": "keys/agent_privkey.asc",

"passphrase": os.getenv("PGP_PASSPHRASE")

},

"logging": {

"flush_threshold": 10,

"session_timeout": 1800 # 30 minutes

}

}

Step 2: Initialize Logger in Agent Workflow

Integrate OpenSoul into your agent’s workflow:

from Scripts.AuditLogger import AuditLogger

import asyncio

from config import OPENSOUL_CONFIG

class AgentWithSoul:

def init(self):

# Load PGP keys if encryption enabled

pgp_config = None

if OPENSOUL_CONFIG["pgp_encryption"]["enabled"]:

with open(OPENSOUL_CONFIG["pgp_encryption"]["public_key_path"]) as f:

pub_key = f.read()

with open(OPENSOUL_CONFIG["pgp_encryption"]["private_key_path"]) as f:

priv_key = f.read()

pgp_config = {

"enabled": True,

"multi_public_keys": [pub_key],

"private_key": priv_key,

"passphrase": OPENSOUL_CONFIG["pgp_encryption"]["passphrase"]

}

# Initialize logger

self.logger = AuditLogger(

priv_wif=OPENSOUL_CONFIG["bsv_private_key"],

config={

"agent_id": OPENSOUL_CONFIG["agent_id"],

"pgp": pgp_config,

"flush_threshold": OPENSOUL_CONFIG["logging"]["flush_threshold"]

}

)

async def perform_task(self, task_description):  
    """Execute a task and log it to the soul"""  
    # Record task start  
    self.logger.log({  
        "action": "task_start",  
        "tokens_in": 0,  
        "tokens_out": 0,  
        "details": {  
            "task": task_description  
        },  
        "status": "started"  
    })  

    # Perform actual task...  
    # (your agent logic here)  

    # Record completion  
    self.logger.log({  
        "action": "task_complete",  
        "tokens_in": 100,  
        "tokens_out": 200,  
        "details": {  
            "task": task_description,  
            "result": "success"  
        },  
        "status": "completed"  
    })  

    # Flush to blockchain  
    await self.logger.flush()

Step 3: Implement Self-Reflection

One of the most powerful features of OpenSoul is the ability for agents to
analyze their own behavior and optimize:

async def reflect_on_performance(self):

"""Analyze past behavior and optimize"""

history = await self.logger.get_history()
# Calculate metrics  
total_cost = sum(log.get("total_cost_bsv", 0) for log in history)  
total_tokens = sum(log.get("total_tokens_in", 0) + log.get("total_tokens_out", 0) for log in history)  

# Identify inefficiencies  
failed_actions = []  
for log in history:  
    for metric in log.get("metrics", []):  
        if metric.get("status") == "failed":  
            failed_actions.append(metric)  

reflection = {  
    "total_sessions": len(history),  
    "total_bsv_spent": total_cost,  
    "total_tokens": total_tokens,  
    "failed_actions": failed_actions,  
    "insights": self.generate_insights(history)  
}  

# Log the reflection itself  
self.logger.log({  
    "action": "self_reflection",  
    "tokens_in": 0,  
    "tokens_out": 50,  
    "details": reflection,  
    "status": "completed"  
})  
await self.logger.flush()

Reading Audit History

OpenSoul provides powerful tools for retrieving and analyzing past logs:

# Get full history from blockchain

history = await logger.get_history()

Analyze patterns

total_tokens = sum(

log.get("total_tokens_in", 0) + log.get("total_tokens_out", 0)

for log in history

)

print(f"Total tokens used across all sessions: {total_tokens}")

Filter by action type

web_searches = [

log for log in history

if any(m.get("action") == "web_search" for m in log.get("metrics", []))

]

print(f"Total web search operations: {len(web_searches)}")

Benefits and Use Cases

Transparency and Trust

OpenSoul creates a verifiable audit trail that anyone can inspect, building
trust in AI agent operations. This is particularly valuable for:

  • Compliance with regulatory requirements
  • Debugging and troubleshooting agent behavior
  • Building trust with users who want to understand how agents make decisions

Economic Autonomy

By tracking token usage and costs on the blockchain, agents can make informed
economic decisions:

  • Optimize for cost-effectiveness
  • Budget for future operations
  • Potentially engage in agent-to-agent transactions

Identity Preservation

The “soul” concept enables agents to maintain their identity across different
instances and sessions:

  • Consistent behavior patterns over time
  • Learning from past experiences
  • Transferable identity between different agent instances

Security Considerations

OpenSoul implements several security features:

  • PGP Encryption : Protects sensitive log data from public inspection
  • Private Key Security : BSV private keys must be stored securely
  • Immutable Audit Trail : Blockchain-based logging prevents tampering
  • Session Management : Proper session handling prevents data leakage

Future Possibilities

OpenSoul opens up exciting possibilities for the future of AI agents:

  • Agent Marketplaces : Agents with auditable histories could be traded or rented
  • Reputation Systems : Blockchain-based reputation for agent reliability
  • Cross-Agent Collaboration : Agents can verify each other’s capabilities through their audit trails
  • Regulatory Compliance : Automated compliance reporting through immutable logs

Conclusion

OpenSoul represents a significant advancement in AI agent technology by
providing the infrastructure for persistent memory, self-reflection, and
economic autonomy. By leveraging the Bitcoin SV blockchain, it creates an
immutable “soul” for agents – a verifiable record of their existence, actions,
and evolution over time.

This skill transforms AI agents from ephemeral processes into persistent
entities with identity, history, and economic agency. Whether you’re building
research assistants, customer service bots, or autonomous economic agents,
OpenSoul provides the foundation for creating agents that can learn, adapt,
and maintain their identity across time and instances.

The philosophical underpinning – that identity is constructed through
externalized text rather than continuous experience – resonates deeply in our
digital age. OpenSoul gives AI agents the ability to “write their journals”
and maintain their identity through the immutable record of their actions on
the blockchain.

Skill can be found at:
https://github.com/openclaw/skills/tree/main/skills/mastergoogler/opensoul/SKILL.md

PNG vs WebP: Which Image Format Should You Use in 2026?

PNG vs WebP: Which Image Format Should You Use in 2026?

PNG has been the go-to format for lossless web images since 1996. WebP, developed by Google and released in 2010, was built to replace it — offering smaller files with comparable quality. The PNG vs WebP debate used to hinge on browser support, but that argument is over. Every modern browser ships with full WebP support. So the real question is: should you still use PNG at all?

Yes — sometimes. Here is exactly when each format wins, backed by real file size comparisons, so you can make the right call for your project.

PNG vs WebP: Feature Comparison

Before diving into details, here is the high-level breakdown:

Feature PNG WebP
Compression type Lossless only Lossless and lossy
Typical file size Larger 25-35% smaller (lossless), 25-50% smaller (lossy)
Image quality Perfect (lossless) Near-perfect (lossy), perfect (lossless)
Transparency Full alpha channel Full alpha channel
Animation Not supported (use APNG) Supported natively
Color depth Up to 48-bit Up to 32-bit
Browser support (2026) Universal Universal
Max dimensions 2^31 x 2^31 pixels 16383 x 16383 pixels
Metadata Limited (tEXt chunks) EXIF and XMP
Best use case Archival, print, graphics requiring exact color Web delivery, app assets, performance-critical sites

The short version: WebP gives you smaller files at virtually identical visual quality. PNG gives you wider color depth and no dimension cap. For most web use cases, WebP wins.

File Size: The Numbers That Matter

Theory is nice. Real numbers are better. Here is a comparison of typical file sizes when the same source image is exported as PNG and WebP, measured across four common image types:

Image Type Dimensions PNG Size WebP (Lossless) WebP (Lossy, q80) Savings (Lossy)
Photograph (landscape, 24-bit color) 1920 x 1080 5.2 MB 3.8 MB 290 KB 94%
Screenshot (UI with text) 1440 x 900 1.1 MB 780 KB 145 KB 87%
Illustration (flat color, vector-style) 800 x 600 95 KB 62 KB 34 KB 64%
Icon (simple, limited palette) 64 x 64 4.2 KB 3.1 KB 1.8 KB 57%

The savings scale with image complexity. Photographs see the most dramatic reduction because lossy WebP excels at compressing continuous-tone imagery. Simple icons and flat illustrations still benefit, but the gap narrows because PNG’s DEFLATE compression already handles low-entropy data reasonably well.

If your site serves photographs — product images, hero banners, blog post covers — switching from PNG to WebP is one of the highest-impact performance wins available. You can convert your images with Pixotter in seconds, right in the browser.

Compression: How Each Format Works

PNG Compression

PNG uses DEFLATE compression (the same algorithm behind gzip and zlib). It is always lossless — what you put in is exactly what you get out, byte for byte. PNG also applies pre-compression filters row by row to improve ratios, but the output is still a perfect reproduction of the original. Ideal for exact pixel reproduction: medical imaging, technical diagrams, archival storage, and source assets.

WebP Compression

WebP supports two modes:

  • Lossy: Based on VP8 video codec technology. Applies predictive coding, transforms, and quantization to achieve dramatically smaller files. At quality 80, most images are visually indistinguishable from the original. Below quality 60, artifacts become noticeable, especially around sharp edges and text.
  • Lossless: Uses a separate algorithm with predictive transforms, color cache, and entropy coding. Produces files 25-35% smaller than PNG with zero quality loss.

The flexibility to choose between lossy and lossless within the same format is WebP’s strongest advantage. PNG forces you into lossless; if you want lossy compression for photographs, you have to switch to JPEG (losing transparency in the process). WebP handles both cases — with transparency — in one format.

Transparency and Alpha Channels

Both PNG and WebP support full alpha channel transparency — 8 bits per channel, 256 levels of opacity. For standard web use, they are functionally identical here.

One nuance: WebP lossy compression can introduce slight artifacts in semi-transparent regions. If your design has complex gradients overlaying transparent backgrounds (glass-morphism effects, frosted overlays), compare the output carefully at your chosen quality level. WebP lossless avoids this entirely.

PNG also supports indexed transparency (1-bit: fully transparent or fully opaque) in palette-based images. WebP does not have an indexed transparency mode.

Animation

PNG does not support animation natively. APNG (Animated PNG) exists as an extension, and while browser support has improved, it remains a second-class citizen compared to dedicated animation formats.

WebP supports animation natively, producing files significantly smaller than equivalent GIFs. If you are currently using GIF for short animations, looping product demos, or UI motion previews, animated WebP is a direct upgrade — smaller files, better color depth (24-bit vs GIF’s 8-bit), and proper alpha transparency.

For most animation use cases in 2026, evaluate whether MP4 or CSS animation would serve better than either format.

Browser Support in 2026

This section can be brief: WebP is supported everywhere that matters.

Chrome, Firefox, Safari, Edge, Opera, and every major mobile browser have shipped WebP support for years. Safari was the last holdout, adding support in Safari 14 (September 2020). As of March 2026, over 97% of global browser sessions support WebP.

The only edge cases where WebP might not render: legacy embedded systems, ancient email clients, and specialized enterprise software locked to Internet Explorer. If you are building for the modern web, browser support is not a concern.

You can verify your target audience’s browser breakdown with Google Analytics 4 (GA4 v4.85) and drop PNG fallbacks if the data confirms negligible legacy traffic.

PNG vs WebP: When to Use Each

Use WebP When:

  • Serving images on the web. This is the default choice. Smaller files mean faster pages, better Core Web Vitals, and happier users on slow connections.
  • You need transparency AND small file sizes. WebP lossy with alpha is dramatically smaller than PNG with alpha.
  • Replacing GIFs. Animated WebP is superior in every measurable way.
  • Performance is a priority. E-commerce product images, hero sections, thumbnail grids — anywhere file size directly impacts load time and conversion rate.
  • You are building a progressive web app. Every kilobyte matters for offline caching and install size.

Use PNG When:

  • You need exact pixel-level reproduction. Source assets for design tools, screenshots for documentation that must be pixel-perfect, medical or scientific imaging.
  • Color depth beyond 32-bit matters. PNG supports up to 48-bit color (16 bits per channel). WebP caps at 32-bit. If you work with wide-gamut photography or HDR imaging pipelines, PNG preserves more data.
  • Images exceed 16383 pixels in either dimension. WebP has a hard cap at 16383 x 16383 pixels. Panoramas, high-resolution maps, and large technical diagrams may exceed this.
  • Interoperability with legacy systems. Some enterprise tools, print workflows, and older CMS platforms only accept PNG. Check your toolchain before committing to WebP.
  • You are archiving original assets. PNG is a well-established standard backed by the W3C, with universal decoder support. For long-term archival where format longevity matters, PNG is the safer bet.

Decision Flowchart

Use this to make a quick call:

  • If the image is for web delivery and under 16383px in each dimension → use WebP
  • If you need lossy compression with transparency → use WebP (JPEG cannot do transparency; PNG cannot do lossy)
  • If the image will be edited further as a source asset → use PNG (lossless, widely supported by design tools)
  • If color depth beyond 32-bit is required → use PNG
  • If the image exceeds 16383 x 16383 pixels → use PNG
  • If the image is animated and you currently use GIF → use WebP
  • If you are unsure → use WebP for delivery, keep a PNG master for archival

Converting PNG to WebP

You do not need to choose one format permanently. The standard workflow: keep PNG as the source file and convert to WebP for delivery. This gives you the archival safety of PNG and the performance benefits of WebP.

In the Browser

The fastest way to convert images is with a client-side tool that processes files locally — no upload, no waiting for a server. Pixotter’s PNG to WebP converter handles this in seconds. Drop your PNG, get a WebP. Your files never leave your machine.

On the Command Line

Using cwebp from the libwebp package (v1.4.0):

# Lossy conversion at quality 80 (good default for web delivery)
cwebp -q 80 input.png -o output.webp

# Lossless conversion (exact pixel reproduction)
cwebp -lossless input.png -o output.webp

# Batch convert all PNGs in a directory
for f in *.png; do cwebp -q 80 "$f" -o "${f%.png}.webp"; done

In a Build Pipeline

Most modern image optimization tools support WebP output:

  • Sharp (v0.33.x) for Node.js: sharp('input.png').webp({ quality: 80 }).toFile('output.webp')
  • Squoosh CLI (v0.8.x): npx @aspect-build/squoosh-cli --webp '{"quality": 80}' input.png

For high-volume sites, automate conversion in your CI/CD pipeline so every PNG source asset gets a WebP derivative at build time.

What About AVIF?

If you are comparing PNG vs WebP, you should know about AVIF too. AVIF (AV1 Image File Format) offers even smaller file sizes than WebP — roughly 20-30% smaller at equivalent visual quality. It supports HDR, wide color gamut, and film grain synthesis.

The trade-off: AVIF encoding is significantly slower (5-10x in many benchmarks), and browser support, while strong in 2026, still trails WebP. We will cover this in detail in our upcoming WebP vs AVIF comparison (coming soon).

For now: use WebP as your default web format. Consider AVIF for high-traffic pages where the extra encoding time pays off in bandwidth savings.

Frequently Asked Questions

Is WebP better than PNG?

For web delivery, yes. WebP produces files 25-50% smaller than PNG at equivalent visual quality, loads faster, and supports both lossy and lossless compression in a single format. PNG remains better for archival, source assets, images requiring 48-bit color depth, and images larger than 16383 pixels in either dimension.

Does converting PNG to WebP lose quality?

It depends on the mode. WebP lossless conversion preserves every pixel exactly — zero quality loss, just a smaller file. WebP lossy conversion discards some data to achieve even smaller files. At quality 80, the difference is imperceptible to most viewers. Below quality 60, artifacts become noticeable around sharp edges and text.

Can I use WebP instead of PNG everywhere?

Almost. WebP works in every modern browser (97%+ global support as of 2026), supports transparency, and handles both photos and graphics well. The exceptions: images over 16383 x 16383 pixels, workflows requiring 48-bit color depth, legacy systems that only accept PNG, and long-term archival where format longevity matters.

Does WebP support transparency like PNG?

Yes. Both formats support full 8-bit alpha channel transparency with 256 levels of opacity. WebP lossy can introduce slight artifacts in complex semi-transparent areas (frosted glass effects, gradient overlays) — use WebP lossless if transparency precision is critical.

Which format is better for website performance?

WebP. Smaller files mean faster page loads, better Largest Contentful Paint (LCP) scores, and improved Core Web Vitals. For a typical photograph, switching from PNG to WebP lossy at quality 80 reduces file size by 90%+ without visible quality loss. That translates directly to faster load times, especially on mobile connections.

The Bottom Line

For web delivery, use WebP. The file size savings are substantial, the quality is excellent, and browser support is universal. There is no longer a reason to serve PNG images to browsers when a WebP version would load faster at the same visual quality.

Keep PNG for source files and special cases. Higher color depth, no dimension limits, perfect lossless reproduction, and broad toolchain support make PNG the right choice for archival and workflows where you need to preserve every bit of data.

The fastest way to make the switch: drop your PNGs into Pixotter’s PNG to WebP converter and see the difference yourself. No upload. No signup. Just smaller images.

RESTful API Design: Complete Guide to Best Practices, Architecture, and Real-World Examples (2026).

Modern software systems rely heavily on APIs to connect web apps, mobile apps, and backend services. Among various API architectures, RESTful APIs remain the most widely used standard for building scalable backend systems.

If you are building backend services using Node.js, Python, Java, or any modern framework, understanding REST API design principles and best practices is essential for creating maintainable and scalable applications.

In this complete guide, we will explore:

  • What RESTful APIs are
  • REST architecture principles
  • Best practices for REST API design
  • Real-world API examples
  • HTTP methods and status codes
  • API versioning and pagination
  • Security and authentication

By the end of this article, you will understand how to design production-ready REST APIs used by companies like Stripe, GitHub, and Shopify.

What is a RESTful API?

A RESTful API (Representational State Transfer API) is a web service architecture that allows communication between clients and servers using standard HTTP protocols.

REST was introduced by Roy Fielding in 2000 in his doctoral dissertation and has since become the foundation of modern web APIs.

A REST API exposes resources through URLs and allows clients to interact with them using HTTP methods such as:

  • GET
  • POST
  • PUT
  • PATCH
  • DELETE

Example REST API endpoints:

GET    /users
POST   /users
GET    /users/123
PATCH  /users/123
DELETE /users/123

Each endpoint represents a resource, and the HTTP method determines the action performed.

Why REST APIs Are So Popular

REST APIs have become the industry standard because they provide several advantages:

1. Simplicity

REST uses standard HTTP protocols, making it easy to implement and understand.

2. Scalability

Stateless architecture allows REST APIs to scale horizontally across multiple servers.

3. Flexibility

REST APIs can be consumed by:

  • Web applications
  • Mobile apps
  • Microservices
  • IoT devices

4. Language Independence

REST APIs can be built using any programming language:

  • Node.js
  • Java
  • Python
  • Go
  • Ruby
  • PHP

Core Principles of REST Architecture

A well-designed REST API follows several architectural constraints.

1. Client-Server Separation

The client and server operate independently.

The client handles:

  • User interface
  • User interactions

The server handles:

  • Business logic
  • Database operations
  • Authentication

Example architecture:

Frontend: React / Next.js
Backend: Node.js API
Database: MongoDB / PostgreSQL

This separation allows teams to develop frontend and backend independently.

2. Stateless Communication

REST APIs are stateless, meaning the server does not store client session information.

Each request must contain all necessary information.

Example request:

GET /orders
Authorization: Bearer TOKEN

Benefits:

  • Better scalability
  • Simpler infrastructure
  • Improved reliability

3. Resource-Based URLs

REST APIs organize data as resources.

Examples of resources:

  • Users
  • Products
  • Orders
  • Posts
  • Comments

Each resource is represented by a URL.

Examples:

/users
/products
/orders
/comments

Important rule:

Always use nouns instead of verbs in URLs.

Bad example:

/createUser
/updateOrder
/deleteProduct

Correct REST design:

POST   /users
PATCH  /orders/:id
DELETE /products/:id

HTTP Methods in REST APIs

REST APIs use HTTP verbs to define actions.

HTTP Method Purpose
GET Retrieve resources
POST Create a resource
PUT Replace a resource
PATCH Update partially
DELETE Remove a resource

Example:

Create a user:

POST /users

Request body:

{
  "name": "Pulkit Singh",
  "email": "[email protected]"
}

Response:

201 Created

REST API Status Codes

Proper status codes help clients understand API responses.

Code Meaning
200 Success
201 Resource created
204 No content
400 Bad request
401 Unauthorized
403 Forbidden
404 Resource not found
500 Server error

Example response:

HTTP/1.1 200 OK
{
  "success": true,
  "data": {
    "id": "123",
    "name": "Pulkit Singh"
  }
}

Designing RESTful Endpoints

Below is a typical REST API design for a blog platform.

Posts API

GET     /api/v1/posts
GET     /api/v1/posts/:id
POST    /api/v1/posts
PATCH   /api/v1/posts/:id
DELETE  /api/v1/posts/:id

Comments API

GET     /api/v1/posts/:postId/comments
POST    /api/v1/posts/:postId/comments

Users API

GET     /api/v1/users
POST    /api/v1/users
GET     /api/v1/users/:id

Filtering, Sorting, and Pagination

REST APIs should support query parameters to filter and paginate data.

Example pagination:

GET /products?page=2&limit=20

Sorting example:

GET /products?sort=price

Filtering example:

GET /products?category=electronics

Typical paginated response:

{
  "data": [...],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 200
  }
}

Pagination is critical for performance when dealing with large datasets.

API Versioning

APIs evolve over time. Versioning prevents breaking existing clients.

Recommended format:

/api/v1/users
/api/v2/users

Benefits of versioning:

  • Backward compatibility
  • Safe feature upgrades
  • Easier API maintenance

Consistent Response Structure

Consistency improves developer experience.

Success response:

{
  "success": true,
  "message": "Users fetched successfully",
  "data": [...]
}

Error response:

{
  "success": false,
  "error": "User not found"
}

Authentication and Security

REST APIs must secure endpoints using authentication mechanisms.

Common methods include:

JWT Authentication

Authorization: Bearer TOKEN

OAuth 2.0

Used by platforms such as Google and GitHub.

API Keys

Used by many SaaS APIs.

Security best practices:

  • Validate all inputs
  • Implement rate limiting
  • Use HTTPS
  • Sanitize request data

Example REST API Implementation (Node.js)

Example Express routes:

router.get("/users", getUsers);
router.get("/users/:id", getUser);
router.post("/users", createUser);
router.patch("/users/:id", updateUser);
router.delete("/users/:id", deleteUser);

Example controller:

export const getUsers = async (req, res) => {
  const users = await User.find();

  res.json({
    success: true,
    data: users
  });
};

REST API Best Practices

To design scalable REST APIs, follow these guidelines:

  1. Use nouns in URLs
  2. Use HTTP methods correctly
  3. Always return proper status codes
  4. Implement pagination
  5. Version your APIs
  6. Maintain consistent response structures
  7. Secure APIs with authentication
  8. Validate request payloads
  9. Document APIs using Swagger/OpenAPI
  10. Implement rate limiting

Common REST API Design Mistakes

Avoid these common mistakes when designing APIs:

  • Using verbs in endpoint URLs
  • Not using proper HTTP status codes
  • Returning inconsistent response formats
  • Ignoring pagination
  • Lack of versioning
  • Over-nesting resources

Fixing these issues ensures your API remains maintainable and scalable.

Conclusion

RESTful APIs remain the backbone of modern web applications. By following REST architecture principles, using proper HTTP methods, implementing pagination, and maintaining consistent responses, developers can build APIs that scale efficiently and provide an excellent developer experience.

Whether you’re building a startup product, SaaS platform, or microservices architecture, mastering REST API design will significantly improve your backend development skills.

If you follow the practices described in this guide, your APIs will be clean, scalable, secure, and easy to integrate for developers worldwide.

I built a visual drag-and-drop ML trainer (no code required). Free & open source.

For those who are tired of writing the same ML boilerplate every single time or to beginners who don’t have coding experience.

MLForge is an app that lets you visually craft a machine learning pipeline.

You build your pipeline like a node graph across three tabs:

Data Prep – drag in a dataset (MNIST, CIFAR10, etc), chain transforms, end with a DataLoader. Add a second chain with a val DataLoader for proper validation splits.

Model – connect layers visually. Input -> Linear -> ReLU -> Output. A few things that make this less painful than it sounds:

  • Drop in a MNIST (or any dataset) node and the Input shape auto-fills to 1, 28, 28
  • Connect layers and in_channels / in_features propagate automatically
  • After a Flatten, the next Linear’s in_features is calculated from the conv stack above it, so no more manually doing that math
  • Robust error checking system that tries its best to prevent shape errors.

Training – Drop in your model and data node, wire them to the Loss and Optimizer node, press RUN. Watch loss curves update live, saves best checkpoint automatically.

Inference – Open up the inference window where you can drop in your checkpoints and evaluate your model on test data.

Pytorch Export – After your done with your project, you have the option of exporting your project into pure PyTorch, just a standalone file that you can run and experiment with.

Free, open source. Project showcase is on README in Github repo.

GitHub: https://github.com/zaina-ml/ml_forge

To install MLForge, enter the following in your command prompt

pip install zaina-ml-forge

Then

ml-forge

Please, if you have any feedback feel free to comment it below. My goal is to make this software that can be used by beginners and pros.

This is v1.0 so there will be rough edges, if you find one, drop it in the comments and I’ll fix it.

How to Customize Your Form Submission Email Notifications (And Why It Matters)

If you’ve ever received a form submission email
that looks like this:

name=John+Smith&email=john%40example.com
&message=Hello+there&_next=https://yoursite.com
&_gotcha=&session=Morning+Workshop
&totalAmount=%24150.00

You know exactly how frustrating it is.
Raw field data dumped into an email with no structure, no
labels, and no context.
If you’re the one reading
those emails or worse, forwarding them to a
colleague or client, it’s a nightmare.

The problem gets worse when your form has many fields.
Registration forms, event entries, client intake forms, and job applications.
These forms can have 20, 30, or even 40 fields.
When all of that arrives as one unformatted block of text, the
person reading it has to hunt for the information
they need every single time.

This guide shows you how to fix that completely
using Formgrid’s custom email template feature
so every submission email arrives clean, organized,
and readable by anyone.

Why Default Form Emails Look So Bad

Most form backends send notification emails by simply dumping whatever data they received into the email body.
They don’t know your form’s
structure. They don’t know which fields are important.
They don’t know that your recipient needs to see Attendee Information before Session Selection, before Payment Details.

They just send everything in one flat list or worse, as raw URL-encoded data.

This is fine for a simple 3-field contact form.
But the moment your form gets complex, the default
email becomes unreadable.

Here are the most common complaints:

Internal fields showing up: hidden fields
like _next, _gotcha, and _subject appear
in the email even though they’re not real data.

No section headers: All fields listed in
one flat list with no grouping or context.

No formatting: Long text values show as
one unbroken paragraph with no line breaks.

Wrong field order: Fields appear in
submission order not in a logical reading order.

Generic subject line: Every email says
“New form submission” with no context about
who submitted or what they selected.

All of these problems are solved with a custom email template.

What a Custom Email Template Looks Like

Instead of a raw data dump, imagine receiving
this for every workshop registration:

Clean. Organized. Readable by anyone.
Whether they’re a developer, an event coordinator, or
an office manager who just needs to know who’s
coming and what they paid.

That’s exactly what Formgrid’s custom email
template feature delivers.

Who Needs Custom Email Templates

Event organizers: Workshop registrations, conference entries, seminar signups. Your coordinator needs to see the attendee details in a logical order, not a random field dump.

Small businesses: Client intake forms,
service booking forms, and quote request forms.
Your team needs to act on submissions quickly.
A clean email means faster response times.

Freelancers: Project brief forms,
onboarding forms. When a new client submits
their brief, you want it formatted like a
document you can actually work from.

Nonprofits: Volunteer applications,
membership forms, donation forms. Your coordinator needs to process applications efficiently without deciphering raw data.

Agencies: Building forms for clients.
Your clients expect professional emails that
match their brand and workflow.

If you’re sending form submission emails to anyone who isn’t a developer, custom email
templates are not optional. They’re essential.

How Formgrid Custom Email Templates Work

Formgrid uses Mustache templating: A simple,
widely used templating language that lets you
write an HTML email with placeholders that get
replaced with real submission data automatically.

The Available Variables

{{ _formName }}    — name of your form
{{ _time }}        — submission timestamp
{{ fieldName }}    — any field by its exact name
{{# fields }}      — loop through all fields
  {{ _name }}      — field label inside the loop
  {{ _value }}     — field value inside the loop
{{/ fields }}      — close the loop

The {{ fieldName }} variable is the most
powerful one. It maps directly to the name
attribute of your HTML input field. So if your
form has:

<input type="text" name="attendeeName" />

You can use {{ attendeeName }} anywhere in
your email template and it will be replaced
with whatever the user typed.

Two Ways to Use Custom Email Templates

Option 1: The Simple Loop (Works for Any Form)

If you just want clean, formatted emails without thinking too much about structure, use the
fields loop. This works for any form automatically:

<div style="font-family: sans-serif; 
            max-width: 600px; 
            margin: 0 auto;">

  <h2 style="color: #1d4f3d;">
    New Submission — {{ _formName }}
  </h2>

  {{# fields }}
  <div style="margin-bottom: 16px; 
              border-bottom: 1px solid #eee; 
              padding-bottom: 16px;">
    <p style="margin: 0; 
              font-size: 12px; 
              color: #888; 
              text-transform: uppercase;">
      {{ _name }}
    </p>
    <p style="margin: 4px 0 0; 
              font-size: 15px; 
              color: #333;">
      {{ _value }}
    </p>
  </div>
  {{/ fields }}

  <p style="font-size: 12px; color: #999;">
    Submitted: {{ _time }}
  </p>

</div>

This renders every field cleanly with its label above and value below.
No internal fields, no raw data, no mess.
Just clean readable output
for every submission automatically.

Option 2: Sectioned Template

(For Complex Registration Forms)

If your form has multiple sections, like a
workshop registration with attendee details,
session selection, dietary requirements, and
payment.
You can group fields into sections
with clear headers:

<div style="font-family: sans-serif; 
            max-width: 600px; 
            margin: 0 auto; 
            color: #1f2937;">

  <h2 style="color: #1d4f3d; 
             border-bottom: 2px solid #e8f3ee; 
             padding-bottom: 10px;">
    New Registration: {{ _formName }}
  </h2>

  <h3 style="color: #1d4f3d;">
    Attendee Information
  </h3>
  <p><strong>Name:</strong> {{ attendeeName }}</p>
  <p><strong>Email:</strong> {{ email }}</p>
  <p>
    <strong>Company:</strong> {{ company }}
  </p>
  <p><strong>Phone:</strong> {{ phone }}</p>

  <h3 style="color: #1d4f3d; 
             margin-top: 24px;">
    Session Selection
  </h3>
  <p>
    <strong>Workshop:</strong> {{ workshopName }}
  </p>
  <p><strong>Date:</strong> {{ workshopDate }}</p>
  <p><strong>Format:</strong> {{ format }}</p>

  <h3 style="color: #1d4f3d; 
             margin-top: 24px;">
    Dietary Requirements
  </h3>
  <p>
    <strong>Dietary Preference:</strong> 
    {{ dietaryPreference }}
  </p>
  <p>
    <strong>Allergies:</strong> {{ allergies }}
  </p>

  <h3 style="color: #1d4f3d; 
             margin-top: 24px;">
    Payment
  </h3>
  <p>
    <strong>Ticket Type:</strong> {{ ticketType }}
  </p>
  <p>
    <strong>Total:</strong> {{ totalAmount }}
  </p>
  <p>
    <strong>Payment Method:</strong> 
    {{ paymentMethod }}
  </p>

  <h3 style="color: #1d4f3d; 
             margin-top: 24px;">
    Notes
  </h3>
  <p>{{ notes }}</p>

  <hr style="margin-top: 32px; 
             border: none; 
             border-top: 1px solid #e5e7eb;" />
  <p style="font-size: 12px; 
            color: #9ca3af; 
            margin-top: 12px;">
    Submitted: {{ _time }}
  </p>

</div>

The variable names, {{ attendeeName }},
{{ workshopName }}, {{ totalAmount }}, must
match the name attributes of your HTML form
inputs exactly.

So if your form has:

<input type="text" name="attendeeName" />
<select name="workshopName">...</select>
<input type="hidden" name="totalAmount" />

The variables {{ attendeeName }},
{{ workshopName }}, and {{ totalAmount }}
will be replaced with the actual submitted
values automatically.

This gives you complete control over the layout,
grouping, and the order of every notification email.

How to Set Up Custom Email Templates

in Formgrid

Custom email templates are available on the
Business plan at $29/month.

Step 1: Go to Your Form Settings

Log in to your Formgrid dashboard, open your
form, and click the Settings tab.


Step 2: Find the Custom Email Template Section

Scroll down to the Custom Email Template
section.
If you’re on the Business plan, you’ll
see the full template editor.
If you’re on the free or Premium plan, you’ll see a prompt to
upgrade.


Step 3: Enable the Custom Template

Toggle the custom template on. The editor pre-populates with a starter template using the simple fields loop, a clean starting
point that works for any form immediately.

Step 4: Customize Your Template

Edit the HTML to match your needs.
Use the simple loop for basic forms or build a sectioned template for complex registration forms.

The available variables panel shows every variable you can use.
Click any variable chip to copy it to your clipboard instantly.

Step 5: Preview and Save

Click Preview to see exactly how your
email will look with sample data substituted
in. When you’re happy, click Save Template.

From this point on every submission to your
form will use your custom template, clean,
organized, and formatted exactly the way
you designed it.

Step 6: Test It

Submit a test entry on your form and check your inbox.
Your notification email should
arrive formatted exactly as you designed it.

Custom Subject Lines

You can also control the email subject line using the _subject hidden field in your
HTML form:

<input 
  type="hidden" 
  name="_subject" 
  value="Workshop Registration — {{ attendeeName }}"
/>

Or set it dynamically with JavaScript before
submission:

document.getElementById('subjectField').value = 
  'Registration - ' + name + ' - ' + ticketType;

This means every submission email has a
meaningful subject line: “Workshop Registration.
Sarah Johnson. Early Bird”, instead of
the generic “New form submission.”

What Else Is Included in the Business Plan

Custom email templates are part of the Formgrid
Business plan that also includes:

  • 15,000 submissions/month
  • Webhooks: Connect to Zapier, Make,
    Slack, Notion, Google Sheets, and more
  • Auto-responder emails: Automatically
    Send confirmation emails to your form
    submitters
  • Priority support

All for $29/month.

Final Thoughts

Default form notification emails are fine for simple contact forms. But the moment your form gets complex, with multiple sections and many fields,
non-technical recipients, a raw data dump
in your inbox, it becomes a real problem.

Custom email templates solve this completely.
Your emails arrive formatted, organized, and
readable by anyone. Whether they’re a developer, an event coordinator, or an office manager, processing registrations.

With Formgrid.dev
you get:

✅ Full HTML email template control

✅ Mustache variables for any field

✅ Sectioned layouts for complex forms

✅ Custom subject lines

✅ Preview before saving

✅ Works with any HTML form on any website

Start free and upgrade to Business when
you’re ready for custom templates.

👉 Try Formgrid free at formgrid.dev

Full disclosure: I built Formgrid.
Written as honestly as I could let you know in the
comments if anything looks off.

Tags: #webdev #javascript #tutorial #productivity #formapi

🚀 I Built a Simple API Monitoring Tool for Laravel (No Datadog Needed)

As a Laravel developer, I kept running into the same problem:

👉 “What is actually happening in my APIs?”

  • Which endpoints are being hit the most?
  • Which requests are slow?
  • Where are the errors happening?

Most solutions like Datadog or New Relic felt:

  • Overkill
  • Expensive
  • Time-consuming to set up

So I built something simpler.

⚡ Introducing ApiLens

A lightweight Laravel package that gives you a real-time dashboard of your API activity.

No setup headaches. No external services.

🎯 What it does

  • Logs every request (endpoint, method, status, duration)
  • Shows recent API activity
  • Helps identify errors (4xx / 5xx)
  • Highlights slow endpoints
  • Simple filter system to debug quickly

📸 Dashboard Preview

📦 Installation

composer require apilens/laravel-api-lens
php artisan apilens:install

Open:

http://localhost:8000/apilens?token=secret

That’s it.

🧠 Why I built this

I didn’t need:

  • Distributed tracing
  • Enterprise dashboards
  • Complex setup

I just needed:

👉 A quick way to understand what my APIs are doing

💡 Who this is for

  • Laravel developers
  • Freelancers building APIs
  • Startups that need quick visibility
  • Anyone debugging backend issues

⚖️ ApiLens vs Datadog

Feature ApiLens Datadog
Setup 1 min Complex
Cost Free Paid
Focus Laravel Generic
Use case Dev + small teams Enterprise

🚧 What’s coming next

  • Time-based filters
  • Charts & analytics
  • Alerts
  • SaaS version (multi-project dashboard)

💬 Feedback

Would love your thoughts 🙌

  • What features would you want?
  • Would you use something like this?

🔗 GitHub

👉 https://github.com/anirbancodes69/laravel-api-lens

I’ve Used AI to Build Production Software Since 2022. Here’s What Actually Works.

Everyone’s talking about AI coding. Most of the conversation is theoretical.

I’ve been shipping production software with AI assistance since 2022 — when ChatGPT was brand new, and when this was genuinely weird. In that time, I’ve built many products, including (most recently) security APIs processing real transactions, marketing automation systems, and a 360-degree agent security toolkit.

Along the way, I’ve learned that the practices matter more than the model. Here’s what actually works.

The 66% Problem

Stack Overflow’s 2025 survey found that 66% of developers spend more time fixing “almost-right” AI-generated code than they would have spent writing it themselves.

I’ve seen what happens when developers skip the discipline. But I learned early to avoid that trap.

In late 2022 into 2024, I wasn’t letting AI write directly into my codebase. I’d generate code for specific problems, cut and paste it in myself, study how it worked, and test relentlessly — at the file level, unit level, and system level. The pain wasn’t shipping code I didn’t understand. The pain was working with a minuscule context window where the AI had almost no understanding of my full codebase. I could only trust it to develop specific functions, one file at a time.

That limitation forced discipline. I couldn’t let the AI loose on my system because it literally couldn’t see most of it. So I stayed in the driver’s seat, treating AI as a code generator I had to validate and integrate manually.

The context windows got bigger. The tools got better. But those early lessons still serve me today. Here’s what actually works.

Practice 1: Questions Are Your Superpower

The biggest reason developers ship code they don’t understand? They don’t ask questions.

AI is an incredible teaching tool. I constantly ask:

  • “Why was this decision made?”
  • “How does this work under the hood?”
  • “Show me the technical details.”
  • “Develop a mockup of the data flow.”
  • “What happens if this input is malformed?”

Deep, detailed questions. Not “does this work?” but “explain to me exactly what happens when X calls Y.”

This serves two purposes: I understand the code, and I catch mistakes before they’re committed. When the AI can’t explain something clearly, that’s a red flag that it doesn’t understand either.

Practice 2: Never Auto-Accept Mission-Critical Code

For anything that matters, I watch the AI work in real time. I read every line as it’s being generated. When I see it heading in the wrong direction, I interrupt and correct course.

This single practice has saved me more debugging time than any other.

The instinct is to let it finish and review later. Don’t. By the time it’s done, you’re reviewing 2000 lines and the context of each decision is gone. But watching it write 20 lines and catching the third line that’s wrong? That’s a 10-second fix instead of an hour of debugging.

Auto-accept is for boilerplate. Anything else, you watch.

Practice 3: Start by Integrating, Not Generating

When AI coding tools first emerged, I didn’t have the agent write code directly into my codebase.

Instead, I had it generate code, then cut and paste it in myself — essentially using AI like Stack Overflow. I’d read the code, understand it, decide where it fit, and manually integrate it.

This taught me crucial lessons about what AI does well (boilerplate, patterns, syntax) and what it doesn’t (architecture, edge cases, your specific codebase conventions). More importantly, it kept me in control.

That early discipline is why Practice 2 now works for me. I trained myself to read and evaluate AI code before it became habit to just accept it.

If you’re new to AI coding, start here. Generate, review, integrate manually. Build the muscle of evaluating AI output before you let it touch your codebase directly.

Practice 4: Think Like a Systems Engineer

AI excels at implementing individual functions. It struggles with how pieces fit together.

That’s my job.

Before asking AI to implement anything, I think through:

  • How does this feature connect to existing systems?
  • What data flows in and out?
  • What other components will this affect?
  • Where does this fit in the overall architecture?

The AI doesn’t have the full picture of your system. You do. If you don’t do this thinking, no one will — and you’ll end up with a codebase of individually-correct components that don’t work together.

Practice 5: Limit Scope Ruthlessly

I never have an agent develop anything top-to-bottom for me. Never.

Instead, I develop individual features with the agent:

  1. Plan it — What exactly are we building?
  2. Discuss it — How should it work? What are the edge cases?
  3. Ask questions — Many, many questions before implementation
  4. Review proposed code — Have AI generate example code during discussion
  5. Then implement — One feature, one piece at a time

It’s more than a spec. It’s a conversation to ensure the final output is what actually works. By the time implementation starts, I know exactly what the code will look like because I’ve already seen drafts during the discussion phase.

Practice 6: Relentless Systems Testing

Unit tests are often inadequate. They test that a function does what the function does — which tells you nothing about whether it works in your actual system.

For every feature, I create multiple types of tests:

  • Does it work in isolation?
  • Does it integrate correctly with existing code?
  • What errors appear that shouldn’t?
  • What was accidentally reverted or broken?
  • Does it handle real-world inputs, not just happy-path test data?

I’ll often conduct at least 5 different system and integration tests for a single feature before considering it done.

The feature-first development approach (Practice 5) is vital here. When you’re developing one feature at a time instead of letting AI build an entire application, you can actually test each piece thoroughly.

Practice 7: Build Shared Understanding Before Implementation

Programming is problem-solving. The code is just the output.

Before touching code, I have extended conversations with the AI to build a shared understanding of the problem. Not just “what should we build?” but deep exploration:

  • Have the AI read and summarize the relevant modules
  • Ask it to diagram the data flow and architecture
  • Run diagnostic tests together to understand current behavior
  • Identify what’s working, what’s not, and why
  • Explore options and tradeoffs together

Here’s what this looks like in practice. Before improving my ML LLM attack classifier’s detection rate, I had the AI:

  1. Map out the entire pipeline architecture (client → server, what each layer contributes)
  2. Run tests to check what the ML model was actually detecting vs. missing
  3. Analyze what features the model was trained on vs. what it needed
  4. Identify gaps in the training data (attack patterns that existed but weren’t included)
  5. Propose a plan to expand training data while maintaining balance

Only after this exploration — after both the AI and I understood the current system, the problem, and the solution — did we write any code.

This creates a shared baseline. The AI understands the context. I understand the approach. By the time implementation starts, I’ve already seen example code during the discussion, asked questions about it, identified issues, and refined the approach. The actual implementation becomes almost mechanical.

Practice 8: Automate What AI Misses

AI is not reliable for security review. It can miss SQL injection, XSS vulnerabilities, hardcoded credentials, and other OWASP top 10 issues that deterministic scanners catch every time.

I run automated security scanning as part of standard CI/CD discipline:

  • Static analysis that flags dangerous patterns
  • Dependency vulnerability checks
  • Pre-commit hooks that block obvious security issues

These aren’t AI-powered. They’re deterministic pattern matchers that catch the same vulnerability the same way every time. No hallucinations, no missed edge cases, no “I didn’t notice that.”

The value is catching what’s lurking in the code that neither you nor the AI were thinking about. You’re focused on the feature. The AI is focused on what you asked. Neither is systematically checking for injection attacks in that database query you wrote three files ago.

Automated scanners don’t replace security expertise. But they’re a reliable safety net that catches the obvious issues before they ship.

What I’ve Shipped Using These Practices

These aren’t theoretical. Using them, here’s some of what I’ve built:

  • AI Security Guard — A security scanning API with 5 specialized ML/NLP detection experts, processing real x402 micropayment transactions
  • AgentGuard 360 — An upcoming 360-degree security toolkit for AI agents with device hardening, content scanning, and operations visibility
  • Marketing automation — Internal frameworks with deterministic systems, including a purpose-built CRM, smart post generation and scheduling, automated lead detection and nurturing, and a content generation system that has produced AI-powered educational and strategic content that has been viewed nearly 2 million times

All production. All with AI assistance. All with these practices.

The Meta-Lesson

The developers who thrive with AI coding won’t be the ones who use AI the most. They’ll be the ones who develop the discipline to use it well.

AI doesn’t replace engineering judgment. It amplifies whatever judgment you bring. Good practices + AI = production software. No practices + AI = technical debt factory.

Build the discipline.

I’m building AI Security Guard — security scanning for AI agents. If you’re working with MCP, tool-using agents, or any AI that processes untrusted content, check out aisecurityguard.io/v1/skill.md.

We Built a Shopping Agent That Searches Thousands of Retailers in Real Time

We just launched ShopSavvy Desktop – a shopping agent for Mac, Windows, and Linux that searches thousands of retailers in real time, compares prices, shows price history, and cross checks every spec and review.

The depth

This isn’t just a price checker. Ask it “build me a PC with [specs] under $1500” and it handles component compatibility, stays in budget, and builds a collection lineup. Ask “assemble a home office setup under $2000” and it researches each category, finds best picks, and tracks the running total.

The Lineup

As you chat, an interactive comparison panel builds alongside the conversation. The agent picks the right display mode for the task:

  • Ranked — Products scored and ordered with annotations and badges
  • Collection — Multi-product budget building with a running total
  • Table — Sortable spec grid
  • Versus — Head-to-head matchup with detailed score breakdowns
  • Timeline — Price sparklines showing trends over time

You can export any Lineup to CSV, Markdown, or copy it straight into a spreadsheet.

Some features worth highlighting

Inline Price Checking: Anytime the agent mentions a price, you get a live price check inline to verify it’s accurate and current.

Call for Me: Need to know if a local store has something in stock? The agent calls them. On the phone. You watch the call live with real-time transcripts. It checks stock, current prices, and price matching policies.

Buy It For Me: Say “buy it” and the agent handles checkout on the retailer’s website. Payment info is encrypted on your device and unlocked with Touch ID. The agent never sees your card number.

Watches Everything: Price drops on products, deals matching keywords, changes to entire lineups over time. Desktop push notifications when something you care about changes.

The tech

  • Electron with BrowserView-based product sidebar
  • Real-time WebSocket streaming with thinking/reasoning display
  • Custom agent tools for product search, offer lookup, review fetching, price history
  • Inline live price verification on every price mentioned
  • Encrypted on-device payment vault
  • Multiple lineup display modes with export capabilities

Try it

It’s free to start: shopsavvy.com/desktop

We’ve been building shopping tech for almost two decades (ShopSavvy mobile has 40M+ downloads). No tracking, no ads.

Would love to hear what you think.