Your app sent the email. Can anyone reply?
I almost shipped a contact form where nobody could reply to us. Not the form itself, which worked. The confirmation email it sent worked too. It went out, the sending API returned a clean 200, the record saved, and the little success toast fired. Everything I could see from inside the app said done. The part I could not see was that if anyone had hit reply on that confirmation, their message would have landed on a domain with no inbox and disappeared, and I would never have known, because a reply that goes nowhere does not generate an error on my side. It generates nothing.
If replies to your app's emails are vanishing, the fix is almost always to stop trusting the send status and go look at the inbound path directly, because a 200 from your email service only proves the message left your server. It says nothing about whether the Reply-To address you stamped on it can actually receive anything. Those are two different questions, and on a vibe coded build they get answered by the same line of code all the time, which is exactly how the reply path dies quietly while the send path stays green.
why do replies to my app's confirmation emails disappear?
Because the address a message is sent from and the address replies come back to are not the same thing, and nothing forces you to notice that until it is already broken. When you send transactional email, the message carries a From header and, if you set one, a Reply-To header. From is the identity the message is sent as. It is outbound only, and to not get flagged as spam it needs the sending domain's authentication records lined up: SPF, DKIM, DMARC, the whole outbound apparatus. Reply-To is the completely separate question of where a human's answer should land. It is inbound. For it to work, that domain needs a real MX record pointing at a mailbox somebody actually reads.
Here is the trap. Early on, your sending address and your reply address are usually the same string, because you are one person with one inbox and you just use it for both. So the code that builds the email uses one constant for both headers, and it works, and you move on. The bug is latent. It only becomes live the day those two addresses need to diverge, which happens the moment you set up a proper verified sending domain and your send identity moves somewhere your inbox does not live. Now the single constant is stamping a Reply-To onto a domain that was built to send and was never built to receive. Every reply hits a wall that returns nothing to you.
the receipt: one constant that answered two questions
I hit this on my own intake flow. The first version built both headers off a single value:
The From address and the reply-to were the same constant, so From: VibeKoded <that address> and replyTo: that same address. It read as obviously correct. It was correct, right up until it wasn't. The day the sending identity moved to a verified send subdomain, that subdomain existed to pass outbound authentication and had no inbound MX at all. If I had simply repointed the one constant, every confirmation would have gone out with a Reply-To on the send subdomain, and every reply from the highest-intent people on the site, the ones who read the confirmation and actually wrote back, would have evaporated. And the whole time, the send would have kept returning 200. Nothing on my side would have logged a single failure, because from the app's point of view there was no failure. The message left. That is all the send status can ever tell you.
What made it fixable before it shipped was noticing that the two headers were answering two different questions that had been collapsed into one field. From needs authentication and points at a domain built to send. Reply-To needs a mailbox and points at a domain built to receive. The fix was not clever. It was splitting the one constant into two: a dedicated send-from address on the verified subdomain, used only as From and never anywhere a human would try to reach, and a separate reply-to address on the apex domain where the inbox actually lives. Two fields, because they were always two questions.
how do I make sure this can't come back?
Encode the requirement where the type system can see it, so a future you cannot forget it. The deeper fix on my build was not just splitting the addresses. It was making the reply-to argument required in the small wrapper that every outbound email goes through. Before, reply-to was optional, which is a polite way of saying a future caller could send mail with no reply path at all and nothing would stop them. An optional field is a bug waiting for a busy afternoon. Making it required means the code will not compile if someone wires up a new email that has nowhere for a human to answer. The check moved out of my memory, where it fails, and into the build, where it holds.
That is the general shape of the move, and it is worth naming because it transfers. The send status is a surface signal. It proposes that things are fine. Whether a real person can receive and reply is the semantic reality underneath, and the surface signal cannot see it. So you go check the semantic layer directly and then you make the invariant structural so it cannot silently regress. On a vibe coded app this matters more than usual, because the AI writing the email code has every reason to produce something that sends and no reason to know that your send domain has no inbox. It will hand you a green 200 and a confident summary, and both will be true, and replies will still be dying. The agent measured the send. Nobody measured the return trip.
the two-minute check that would have caught it
You do not need a test harness for this. You need to actually use the inbound path once, the way a real recipient would. Trigger the email to an address you control, open the message, hit reply, send that reply, and confirm it lands somewhere a human reads. If it bounces or silently disappears, your Reply-To is pointed at a domain that cannot receive, and you just found it in two minutes instead of finding it a month later when a lead mentions they emailed you back and heard nothing. While you are in there, check the Reply-To header on the raw message and ask the one question that matters: does that domain have an inbox, or does it only know how to send? A vibe coder shipping fast can skip a lot of things safely. The reply path on the email you use to talk to your own users is not one of them.
If you are building custom app plumbing like this, the transactional email, the intake flow, the confirmation that a real person is on the other end of, and you want a second set of eyes that checks the inbound path and not just the green light, that is the kind of thing I help with. If it is useful to have someone shape this for your own build, /work-with-us. Otherwise take the two-minute check and go make sure the last email your app sent has somewhere for the answer to land.
// part of the custom apps topic
// grab the free starter kit that makes your AI stop forgetting and stop guessing: get it →
// building with AI? the field manual has the structured lessons.
// hitting this on a real build? this is what I fix →