Use Mailgun in a Wasp app and set a Reply-To header. Wasp’s built-in email sender doesn’t expose provider headers, so we call Mailgun’s REST API with a tiny server helper and reuse Wasp env vars.
- Domain reputation drives deliverability. Good rep = inbox; bad rep = spam.
- Split domains/subdomains: marketing vs transactional to protect reputation.
- Reference: Mailgun FAQ — How do I pick a domain name?
- Verification and reset emails accept only subject/text/html via
getEmailContentFn. - No custom headers there (so
reply-tofield cannot be used).
- Use a small helper that posts to Mailgun and sets
h:Reply-To. - Reuse Wasp envs:
MAILGUN_API_KEY,MAILGUN_DOMAIN,MAILGUN_API_URL(EU optional).
Add to .env.server.
MAILGUN_API_KEY=your_api_key
MAILGUN_DOMAIN=email.domain.com
# Optional EU endpoint
MAILGUN_API_URL=https://api.eu.mailgun.netSupports Reply-To via h:Reply-To.
Location: app/src/server/email/mailgun.ts
Signature:
sendMailgunEmail({
from: string,
to: string,
subject: string,
text?: string,
html?: string,
replyTo?: string,
})Example (server-side):
import { sendMailgunEmail } from '@src/server/email/mailgun';
await sendMailgunEmail({
from: 'Dude <hello@email.domain.com>',
to: 'user@example.com',
subject: 'Hello!',
html: '<p>Hi there 👋</p>',
replyTo: 'contact@domain.com',
});Handy for manual tests:
- Page:
EmailTestPageat/email-test(requires auth) - Action:
sendTestEmailinapp/src/email-test/operations.ts
Client usage:
import { useAction, sendTestEmail } from 'wasp/client/operations';
const send = useAction(sendTestEmail);
await send({
fromName: 'Dude',
fromEmail: 'hello@email.domain.com',
to: 'user@example.com',
subject: 'Hello',
body: '<p>Hello world</p>',
isHtml: true,
replyTo: 'contact@domain.com',
});