Skip to main content
Contact us
Legacy admin dashboard and e-commerce platform

Project Story

An AI obituary writer for families, and an API that moderates obituaries

Families on Legacy.com answered questions about the person who died, and the writer we built turned their answers into a draft obituary they could edit, restyle and publish. We built it for Legacy.com on Next.js, tRPC, Prisma and OpenAI's models, then opened the same models up as APIs, including one that checks any obituary against Legacy's content rules and returns structured flags. Legacy moved families over to a writer of its own in November 2025.

Client
Legacy.com
Industry
Funeral services
When
2023 to 2025
Status
Writer replaced by Legacy's own in November 2025
  • Next.js
  • TypeScript
  • tRPC
  • Prisma
  • PostgreSQL
  • OpenAI API
  • Zod
  • Stripe
  • Vercel
  • Mailgun
  • Sentry
  • Datadog
  • PostHog

A form a grieving family can get through

Legacy.com hosts obituaries for more than 70% of deaths in the United States. In February 2023 we started building ObitWriter for it, a guided writer that takes what a family knows about the person who died and gives them back a draft obituary.

The first version had short single-line inputs. Within three weeks we’d swapped several of them for text areas, so families could answer in their own words. The form that stuck asks for the basics (name, dates, where they died) and then a set of optional prompts, each with its own box:

  • what they were like
  • surviving and predeceased family
  • schools, military service and career
  • places of worship, hobbies, memorial services and charities
  • the funeral home handling things

Before the draft, the writer asked where the family planned to publish it. The options were Legacy.com, a newspaper (with over 2,700 to choose from), social media, the funeral home, or no plans yet. That answer decided where the order went later. Funeral homes and cremation providers could also run the writer on their own sites, and in that version the provider’s details were already known, so the form skipped those questions.

The draft comes back on a page with three tools. Families can edit the text directly, open Adjust AI to rewrite it in a Traditional, Religious, Poetic or Playful tone, or open Versions to go back to any earlier draft.

A made-up family answering a few of the writer's prompts, then a draft obituary built only from those answers, with tone, version and edit tools under it.

Keeping the model to what the family said

The worst thing this tool could do is say something untrue about someone’s mother. A lot of the prompt is there to stop that.

The server builds the request from the answered fields only. An empty prompt is left out entirely, so the model never sees a “Hobbies” heading with nothing under it and tries to fill it in. Each group of answers carries its own instruction. Military service is written “as though the audience understands military lingo.” Religion is mentioned only if the family gave details, and the model is told not to guess. Phone numbers are excluded.

The system message casts the model as a newspaper obituary editor, tells it to use only the data the family provided, and to write a short death notice instead of a full obituary when only the basic details are filled in. One line in it says the audience “is easily offended if you make claims about their deceased loved one that are not true.”

Temperature took a few tries. In February 2023 we raised it from 0.7 to 0.9, and later settled the first draft at 0.6. The Creativity slider in Adjust AI runs from 1 to 5, and the server maps that to a temperature between 0.2 and 1.0, with the middle setting landing on the same 0.6 as the first draft.

A tone change sends the whole conversation so far, then asks the model to rewrite the draft with that tone’s instruction (“give it a more religious tone,” “add some more poesy and artistry”). That way the model is revising the draft it just wrote, with the family’s answers still in front of it.

ObitWriter started on GPT-3.5 Turbo. Tone rewrites moved to gpt-4-1106-preview in January 2024, and in June 2024, with 3.5 Turbo about to be deprecated, first drafts moved to gpt-4-turbo.

Every draft is a row

Each generation is saved as a Completion row on the family’s Session, with the model, tone and temperature that produced it. The session points at the one the family has chosen through selectedCompletionId, and that’s the text that gets published. The Versions panel is just a list of those rows.

It didn’t start out that clean. Until February 2024 the obituary text also lived in a column on the session and another on the order, and the copies drifted, so an admin or a publish could pick up the wrong version. We moved everything onto Completion, split it into text (the clean obituary) and textWithPrompt (with the AI helper text, which remixes need), backfilled selectedCompletionId with each session’s newest draft, and exported the old columns to CSV before dropping them.

Free obituaries needed a check

Legacy made some obituaries free to publish, and that opened the door to a false or prank obituary landing on Legacy.com. The fix was to ask who is handling the body, and have someone check.

Every family publishing through the writer enters the provider handling final disposition: whether it’s a funeral home or a cremation provider, its name, phone number, city and state. Providers running the writer on their own site skip this, since we already know who they are. The obituary publishes right away, and the order lands in a Disposition Verification queue in the admin we built, showing the provider details, decedent information and the obituary itself. Legacy’s support team confirms it or rejects it. Rejecting deletes the obituary from Legacy.com through Legacy’s API, and each rejection is stored with the admin who made it.

Whether verification is required at all is a setting in the admin (final_disposition_verification), stored in the database and validated at build time alongside the environment variables. We got the first release wrong. Paid orders were landing in the queue along with free ones, and in April 2024 a hotfix limited verification to free orders.

In July 2024 Legacy wanted a one-week test of whether more families publish when it costs nothing. Every path to publishing on Legacy.com went free behind settings that could be flipped back quickly, and Legacy warned its verification team to expect a spike. The result of that test stayed with Legacy, and we don’t have it.

The same models as APIs

We also opened the models up through three API routes, for Legacy and partners it approved.

POST /api/obituaries/generate takes the form’s fields as JSON, validated with Zod, where only the first name, last name and date of death are required. Callers authenticate with a bearer key we issue. A second mode takes an existing obituary and a tone and returns a rewrite. The API’s prompt used to ask for a simpler obituary than the writer’s, and in May 2024 we removed that line so API callers and families get the same prompt.

POST /api/obituaries/processor/v2 takes an obituary someone has pasted in and rewrites it as a plain, factual notice in two passes. The first pass pulls the facts out as JSON: name, pronouns, age, birth and death dates and places, services, schools, employer, military branch and a charity, with “undefined” wherever the text doesn’t say. It runs under strict rules. No days of the week, times on a 12-hour clock, no officiants, no links, and military service only if the person served. The second pass gets a prompt assembled from just the defined facts and writes a short notice in its own words, paragraphs of three sentences or fewer, full name used once. Splitting it in two means the rewrite only ever sees facts that the first pass found in the original.

The third route is moderation.

Checking an obituary against Legacy's rules

POST /api/obituaries/moderation/v1 takes an obituary and an api_key header and returns a list of flags. Each flag has a flag_id, the bad_content copied word for word, and the rule_broken. An empty list means nothing was found. We built it in late 2023, and by March 2024 Legacy was using it to moderate obituaries on another of its platforms, outside the writer.

A request goes through three layers.

The model comes first, gpt-4o in JSON mode at temperature 0.5. Its first job is to decide whether the text is an obituary at all. If it is, it flags anything that breaks Legacy’s list. The published version has 23 rules, and they’re specific to obituaries:

  • song or poem lyrics longer than three lines, and quotes or other copyrighted text
  • obituaries for pets
  • birth or death years in the future, worked out from the current year on every request, with service dates exempt
  • complaints about what the obituary or newspaper costs, and requests to text someone
  • non-English text, misspellings, and odd punctuation or line breaks
  • slurs, profanity (including inside hashtags), hate groups and a list of hate acronyms, drug references, addiction and violence, including family fights
  • Native American team names such as the Redskins and the Cleveland Indians

The prompt also says what not to flag. Death itself isn’t hateful, and non-hateful mentions of gender transition are fine.

Next come a few fixed phrase checks Legacy asked for, plain string matches that always run. “Only regret,” for example, comes back flagged with the note “check for blame or other content issues,” which reads like an instruction to the person reviewing it.

Last, if the caller turns it on, a word list. Each word is matched as a whole word with a regex after email addresses are stripped out, so an address that happens to contain a listed word doesn’t trip it. Each hit becomes its own flag.

Legacy kept adding rules after launch, in November 2024, July 2025 and August 2025, the last one a variant spelling for the team-name rule. One weakness we know about is that if the model’s reply ever fails to parse, the service logs it and returns only the phrase and word-list flags. In that case it fails open, and nothing in the response says the model’s check didn’t run.

A made-up obituary passing through the moderation API's checks and coming back with two flags, a death year in the future and lyrics over three lines, so it goes to a reviewer.

What ran around it

The writer was a T3 stack app (Next.js, tRPC, Prisma, Postgres) on Vercel, with Stripe for paid orders and Mailgun for email. Published obituaries went to Legacy’s obituaries API, and newspaper orders to Legacy’s newspaper partner, whose list of papers a cron refreshed every night. Other Vercel crons handled the rest:

  • an hourly SFTP upload to Legacy of a CSV of orders created six to seven hours earlier and still not published
  • reminder emails to families with an unfinished draft, at 24 and 48 hours, the second with a discount
  • a daily report of obituaries created and published and the revenue from them, with CSVs for yesterday, month to date and year to date

In September 2024 we raised every API route’s time limit to 300 seconds. Errors went to Sentry and Datadog, and product analytics to PostHog. Honestly, this codebase never had an automated test suite. Changes went through a staging deployment before production, across 398 merged pull requests. If we built it today, the prompt-building code and the moderation parser are the first things we’d put under tests.

In November 2025 Legacy launched its own guided obituary writer and asked us to point the old one at it. We shipped the permanent redirect the next day. We still keep the API project current, most recently moving it to Node 24 in September 2026. We also built MarketIQ, Legacy’s monthly report for funeral brands.

Related case study

Legacy

Questions about this project

HQ, a software design and development agency in Ogden, Utah, built ObitWriter for Legacy.com, which hosts obituaries for more than 70% of deaths in the United States. Families answered a guided form about the person who died, and the writer used OpenAI's models to draft an obituary from only those answers, which the family could edit, rewrite in a different tone, compare against earlier versions and publish to Legacy.com or a newspaper. HQ built it from February 2023 and Legacy moved families to its own writer in November 2025.

The moderation API HQ built for Legacy.com takes an obituary in a POST request with an API key and returns a JSON list of flags, each with a flag ID, the offending text copied word for word and the rule it broke. It runs three layers. GPT-4o in JSON mode first decides whether the text is an obituary at all and then checks it against Legacy's obituary-specific rules, such as lyrics over three lines, pet obituaries, future death years and slurs. Fixed phrase checks Legacy requested always run, and an optional word list matches whole words after stripping email addresses. An empty list means nothing was found. Legacy used it to moderate obituaries on another of its platforms.

HQ built the request from the answered fields only, so a blank prompt never reached the model as an empty heading it might fill in. The system prompt told the model to use only the information the family provided, to write a short death notice when only basic details were given, and not to guess at things like religion. For rewriting existing obituaries, HQ split the work into two model calls, one that extracts facts into JSON with "undefined" for anything missing and one that writes only from the facts found.

Families publishing through ObitWriter entered the funeral home or cremation provider handling final disposition, with its name, phone number, city and state. The obituary published right away and the order went into a verification queue in an admin HQ built, where Legacy's support team confirmed or rejected it. Rejecting an obituary deleted it from Legacy.com through Legacy's API. Verification could be switched on and off as a database setting, and after an early bug pulled paid orders into the queue, it applied only to free orders.

HQ built ObitWriter on the T3 stack, Next.js, TypeScript, tRPC and Prisma with PostgreSQL, hosted on Vercel. It used OpenAI's API, moving from GPT-3.5 Turbo to GPT-4 Turbo in June 2024, with GPT-4o for moderation. Stripe handled paid orders, Mailgun sent email, Vercel cron jobs ran an hourly SFTP export, reminder emails and daily reports, and Sentry, Datadog and PostHog covered errors, logs and analytics.

Let's build together

Whether you're looking to design, build, update, or refresh an existing app or website, we can help. Even if we're not a good fit, we will help point you to somebody who might be.

Contact us