Tutorials / Local development

Tutorial · any language · 10 minutes

Test your email webhook handler on localhost

Your webhook handler runs on your laptop, but email arrives on the internet. The GhostParse CLI closes that gap two ways: it simulates signed webhooks offline, and it streams real email to localhost as it arrives. No tunnel, no deploy.

▶ Watch the video walkthrough on YouTube

1. Simulate a webhook, with no account

Start your app, then point the sandbox at it. It posts a realistic email.received event, signed exactly as real ones are:

npx @wildye/email-parser-cli simulate http://localhost:3000/webhooks/email
! No signing secret given, so this test one is used: whsec_5mY0B…
  Give your app the same secret (or pass --secret with your real one) so it accepts these webhooks.
10:42:07 200 jane@sample.invalid → support@yourcompany.example "Re: Your order #1042 has shipped" 14 ms

Pass --secret whsec_… with the secret your app uses (from Dashboard → Inbound webhook once you have an account). Every simulated event has data.sandbox: true, so your code can tell a test from real mail.

2. Every event, and the awkward cases

email-parser simulate http://localhost:3000/webhooks/email --event bounced      # an email you sent bounced
email-parser simulate http://localhost:3000/webhooks/email --event complained   # marked as spam
email-parser simulate http://localhost:3000/webhooks/email --repeat 3           # the same webhook-id three times
email-parser simulate http://localhost:3000/webhooks/email --bad-signature      # a forgery: must be refused
  • --repeat sends the same webhook id each time, the way a retry would: your handler should store it once.
  • --bad-signature passes only if your endpoint answers 4xx. If it says your endpoint accepted it, you aren't verifying signatures.

3. Your own emails, through your own parser

Save any email as an .eml file (in most mail apps: Save as or Download message). With an API key, the sandbox parses it with the same parser real mail goes through, and with --parser, adds what your rules parser extracts:

export EMAIL_PARSER_API_KEY=ek_live_…
email-parser simulate http://localhost:3000/webhooks/email --eml ./invoice.eml --to invoices@yourcompany.com
email-parser simulate http://localhost:3000/webhooks/email --eml ./invoice.eml --parser prs_…

Nothing is delivered, stored or counted against your plan. Add --print to see the event's JSON.

4. Real email, streamed to localhost

When you're ready for the real thing, listen forwards every email your account receives to your local URL, unchanged and still signed:

email-parser listen --forward http://localhost:3000/webhooks/email

Now send an email to one of your addresses and watch it arrive. Or send our realistic sample through the whole pipeline with email-parser sample.

5. Check your handler verifies signatures

The one thing every handler must do. With our SDKs it's one line:

import { verifyWebhook } from "@wildye/email-parser";
const event = verifyWebhook(rawBody, req.headers, process.env.GHOSTPARSE_WEBHOOK_SECRET); // throws if forged

Then email-parser simulate … --bad-signature should print Your endpoint rejected the bad signature. More languages in the docs.

Next