Recipe: x402 server email-fulfillment
A server protecting a "get me a license key" endpoint. After on-chain settlement, it emails the buyer.
import express from "express"
import { createX402bServerExpress } from "@bosonprotocol/x402-server-express"
import { ChannelRegistry, type FulfillmentChannel } from "@bosonprotocol/x402-fulfillment"
import { Resend } from "resend"
import { ethers } from "ethers"
const resend = new Resend(process.env.RESEND_API_KEY)
const wallet = new ethers.Wallet(process.env.SELLER_PRIVATE_KEY!)
const emailChannel: FulfillmentChannel = {
id: "email",
async fulfill({ buyer, exchangeId, deliveryInfo }) {
const licenseKey = await mintLicenseKey(exchangeId)
await resend.emails.send({
from: "licenses@example.com",
to: deliveryInfo!.email,
subject: "Your license key",
text: `Thanks! Your key: ${licenseKey}`,
})
},
}
const x402 = createX402bServerExpress({
network: "polygon",
chainId: 137,
escrow: {
configId: "production-137-0",
sellerId: "12",
productUuid: "ec00f1a5-2c96-4d22-9d80-2a7c12345678",
fulfillment: { channels: ["email"] },
},
signer: wallet,
facilitator: { url: "https://facilitator.bosonprotocol.io" },
channelRegistry: new ChannelRegistry([emailChannel]),
})
const app = express()
app.get("/api/license", x402.middleware(), async (_, res) => {
// Middleware already fulfilled via the email channel.
res.json({ ok: true, exchangeId: req.x402!.exchangeId })
})
app.listen(3000)Buyer side
The buyer client supplies their email in the X-PAYMENT envelope:
await fetchWithPayment("https://api.example.com/license", {
headers: {
"x402-fulfillment": JSON.stringify({ channel: "email", email: "buyer@example.com" }),
},
})Idempotency
Persist (exchangeId) -> emailedAt in your DB. If a reorg or replay re-fires the redemption event, don't re-email.