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:
Whatever your use case, you'll need to solve the inbound email problem.
The DIY approach. You set up Postfix, Haraka, or similar, configure MX records, handle SMTP connections, parse MIME messages, deal with spam filtering...
Pros:
Cons:
Unless you're building an email company, this is overkill.
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:
Cons:
Good for low-volume, non-time-sensitive use cases.
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:
Cons:
This is what most production apps should use.
[email protected] or a custom domainNo mail servers. No IMAP connections. Just HTTP.
Let's say you're building a support system. Here's the complete flow:
// 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 })
})With Mailhooks, you'd create an address like [email protected] or use your own domain ([email protected]) and point it to our servers.
Emails to your support address now create tickets automatically. No cron jobs, no polling, no mail server maintenance.
You can! Both offer inbound email parsing. But:
Mailhooks is purpose-built for receiving email. It's our entire focus, not an afterthought feature on a sending platform.
Your webhook receives clean, parsed JSON. No MIME parsing, no charset headaches, no attachment encoding issues. Just data you can use.
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.
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:
Have questions? Check out our documentation or reach out — we're happy to help you get set up.