How to Receive Emails in Your App
A practical guide to receiving and processing incoming emails in your application. Learn the different approaches and find the simplest solution.
Your app can send emails easily — every framework has libraries for that. But what about receiving emails? That's where things get complicated.
Maybe you want to:
- Let users reply to notifications by email
- Build a support ticketing system
- Parse incoming invoices or receipts
- Create an email-based workflow automation
Whatever your use case, you'll need to solve the inbound email problem.
The Three Approaches
1. Run Your Own Mail Server
The DIY approach. You set up Postfix, Haraka, or similar, configure MX records, handle SMTP connections, parse MIME messages, deal with spam filtering...
Pros:
- Full control
- No per-email costs
Cons:
- Massive maintenance burden
- Security responsibility (mail servers are attack targets)
- Deliverability reputation to manage
- IP warming, blacklist monitoring, etc.
Unless you're building an email company, this is overkill.
2. Poll via IMAP
Connect to an IMAP server (Gmail, Outlook, or your own) and periodically fetch new messages.
// Pseudo-code: IMAP polling
const imap = new ImapClient(config)
setInterval(async () => {
const messages = await imap.fetch('INBOX', { since: lastCheck })
for (const msg of messages) {
await processEmail(msg)
}
}, 60000) // Check every minutePros:
- Works with existing email accounts
- No infrastructure changes
Cons:
- Polling means delays (1-5 minute latency typical)
- Connection management headaches
- Rate limits on providers like Gmail
- Doesn't scale well with high volume
Good for low-volume, non-time-sensitive use cases.
3. Email to Webhook
The modern approach: emails arrive, get converted to HTTP requests, and hit your webhook endpoint instantly.
// Your webhook handler
app.post('/webhook/email', (req, res) => {
const { from, subject, body, attachments } = req.body
// Process immediately - no polling!
await createTicket({ from, subject, body })
res.status(200).send('OK')
})Pros:
- Real-time (sub-second delivery)
- No infrastructure to manage
- Just HTTP — works with any stack
- Attachments included in payload
Cons:
- Requires a service provider
- Per-email costs (usually minimal)
This is what most production apps should use.
How Email-to-Webhook Works
- You get a receiving address — like
[email protected]or a custom domain - Emails arrive at that address
- The service parses the email — extracts from, to, subject, body (text + HTML), attachments
- Your webhook gets called with a clean JSON payload
- You process it — create ticket, trigger workflow, whatever you need
No mail servers. No IMAP connections. Just HTTP.
Example: Support Ticket System
Let's say you're building a support system. Here's the complete flow:
1. Set up your webhook endpoint
// Express.js example
app.post('/webhooks/support-email', async (req, res) => {
const {
from, // "[email protected]"
fromName, // "Jane Customer"
subject, // "Re: Order #12345"
text, // Plain text body
html, // HTML body (if sent)
attachments, // Array of { filename, contentType, content }
messageId, // For threading
inReplyTo, // If this is a reply
} = req.body
// Find existing ticket or create new one
const ticket = await findOrCreateTicket({
email: from,
subject: subject.replace(/^Re:\s*/i, ''),
})
// Add the message
await ticket.addMessage({
from: fromName || from,
body: text || stripHtml(html),
attachments,
})
// Notify your team
await notifySlack(`New message on ticket #${ticket.id}`)
res.status(200).json({ received: true })
})2. Configure your receiving address
With Mailhooks, you'd create an address like [email protected] or use your own domain ([email protected]) and point it to our servers.
3. That's it
Emails to your support address now create tickets automatically. No cron jobs, no polling, no mail server maintenance.
Why Not Just Use SendGrid/Mailgun?
You can! Both offer inbound email parsing. But:
- SendGrid Inbound Parse — works, but configuration is clunky and the payload format hasn't been updated in years
- Mailgun Routes — solid, but you're locked into their ecosystem
Mailhooks is purpose-built for receiving email. It's our entire focus, not an afterthought feature on a sending platform.
Getting Started
- Sign up at mailhooks.dev
- Create your first hook — pick an address, set your webhook URL
- Send a test email — see the payload in your logs
- Build your integration — usually takes less than an hour
Your webhook receives clean, parsed JSON. No MIME parsing, no charset headaches, no attachment encoding issues. Just data you can use.
Use Our SDK
For an even smoother experience, use our official Node.js SDK:
npm install @mailhooks/sdkimport { Mailhooks } from '@mailhooks/sdk'
const mailhooks = new Mailhooks({ apiKey: process.env.MAILHOOKS_API_KEY })
// List recent emails
const emails = await mailhooks.emails.list({ limit: 10 })
// Get a specific email
const email = await mailhooks.emails.get('email_123')
// Access parsed data
console.log(email.from, email.subject, email.text)The SDK handles authentication, pagination, and gives you full TypeScript support. Check it out on npm.
Realtime with Server-Sent Events
Need to react to emails instantly without setting up webhooks? Use our SSE stream:
import { Mailhooks } from '@mailhooks/sdk'
const mailhooks = new Mailhooks({ apiKey: process.env.MAILHOOKS_API_KEY })
// Subscribe to realtime email events
const stream = mailhooks.emails.stream()
stream.on('email', (email) => {
console.log('New email received:', email.subject)
// Process immediately — no polling, no webhooks to configure
})
stream.on('error', (err) => console.error('Stream error:', err))SSE is perfect for:
- E2E testing — wait for verification emails in your test suite
- Development — see emails arrive in real-time while building
- Simple integrations — when you don't want to expose a webhook endpoint
Have questions? Check out our documentation or reach out — we're happy to help you get set up.