In-App Notification Emails for Mentions and Comments Without Spamming
Email users about mentions, comments and assignments without flooding them: batching windows, per-type preferences, threaded subjects, and the API request.…
The fastest way to get your product's emails filtered to spam is to send one for every comment. Users mute the sender, mark it as spam, or unsubscribe from everything, and the one notification that mattered goes unread. Here is a notification email design that respects attention: what gets an email at all, how to batch, how to make threads readable in a mail client, and how to send it.
Quick answer
Email immediately only for events that are about the user personally and need action (a direct mention, an assignment, a reply to their comment). Batch everything else into a window of 10 to 30 minutes per user and send one email per window. Suppress emails while the user is active in the app, let them set preferences per event type, and use the same subject for every email in a thread so mail clients group them.
Classify events before you email
- Immediate: direct @mention, task assigned to you, reply to your comment, request for your approval. These justify one email each, subject to the active-user suppression below.
- Batched: new comments on threads you follow, status changes on items you watch, new members in your project. One email per window listing all of them.
- Never emailed by default: likes, reactions, edits, "X viewed your document". In-app only, unless the user opts in.
- Digest: anything older than a day that was never opened in-app goes into the weekly summary, not a separate email.
Batching that feels instant
- When a batched event occurs, write it to a pending_notifications table with the user id and created_at, and start (or extend) a per-user timer: send_at = now + 15 minutes if no timer is pending.
- A scheduler runs every minute, picks users whose send_at has passed, gathers all their pending rows, renders one email, sends it, and deletes the rows.
- If the user opened the app after the events were created, drop the rows silently; they have seen them.
- For immediate events, skip the timer but still check active-user suppression: if the user has had the app open in the last 2 minutes, show it in-app only.
- Store notification_email_sent_at per event so retries never resend.
Make threads readable in the inbox
Users often follow a discussion from their mail client. Give every email about the same thread the same subject, for example "Re: Q3 launch plan (Acme)", so Gmail and Apple Mail group them into a conversation. Lead with who did what, then quote the comment text, then one link to the exact comment. Include enough context (the document title, the project) that the email makes sense on its own. Put the reasons the user is receiving it in the footer ("You are receiving this because you follow this document") with a one-click link to stop following that specific thread, which is the unsubscribe people actually want.
The send request
const res = await fetch("https://api.oqumail.com/api/v1/emails", {
method: "POST",
headers: {
"Authorization": "Bearer " + process.env.OQUMAIL_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
from: "notifications@yourdomain.com",
to: user.email,
subject: "Re: " + thread.title + " (" + workspace.name + ")",
html: '<p><strong>' + escapeHtml(actor.name) + '</strong> mentioned you in ' + escapeHtml(thread.title) + ':</p><blockquote>' + escapeHtml(comment.text) + '</blockquote><p><a href="' + comment.url + '">Reply in Acme</a></p><p style="font-size:12px;color:#666">You were mentioned directly. <a href="' + unfollowUrl + '">Stop emails for this thread</a> · <a href="' + prefsUrl + '">Notification settings</a></p>',
text: actor.name + " mentioned you in " + thread.title + ":\n\n" + comment.text + "\n\nReply: " + comment.url
})
});
if (!res.ok) throw new Error("send failed: " + res.status);Preferences that people understand
Offer a settings page with one row per event type and three options: immediately, in a batch, or never, plus a global "pause all email for 24 hours". Default new users to immediate for personal events and batched for the rest. Apply the preference at send time, not at event time, so changing a setting takes effect for events already pending. Every notification email links to this page.
Sending from the right address
Use notifications@ (or a per-workspace address like acme-notifications@) on your own domain, and make the from name the actor's name where you can ("Jane via Acme"), which lifts open rates without pretending to be Jane. Keep a monitored support@ on the same domain, because people reply to notification emails with the answer to the comment. On OquMail the API and the mailboxes share the domain on the free plan, SPF, DKIM and DMARC are set up with live verification when you add the domain, and each message gets a delivery log with the receiving server's SMTP response, useful when a user says a mention email never arrived.
Common questions
Can users reply to the email to post a comment?
Reply-by-email requires an inbound mailbox and parsing, which is a bigger project. Start with a reply link in the email; add reply-by-email later using a mailbox on the same domain read over IMAP.
How long should the batch window be?
Fifteen minutes suits most work tools. Shorter than five feels like a firehose; longer than thirty makes the email feel stale.
Should I email at night?
Batched emails can wait until the user's morning; hold them and release at 8 a.m. local. Immediate events (assignments) can go anytime, since the user has their own do-not-disturb settings.
Free business email on your own domain
OquMail gives you up to 15 mailboxes on your domain — free — with guided SPF/DKIM/DMARC, webmail, IMAP/SMTP for any mail app, and a send API. Most teams are live in under fifteen minutes. Start at oqumail.com.
Get started free