HelpStackDocs

Integrations

Email Channel

Connect a mailbox as a HelpStack channel: inbound mail is polled over IMAP and replies are sent over SMTP.

Quick facts#

Channel typeEMAIL
InboundIMAP polling worker (npm run email:poll)
OutboundSMTP via nodemailer (channel credentials)
CredentialsStored encrypted (AES-256-GCM) per channel
Transport securityIMAP over TLS
Poll interval~10s (dev) / ~60s (prod)

Inbound: IMAP polling#

A background poller connects to each active EMAIL channel's IMAP server (TLS) and, on each cycle:

  1. Fetches recent unprocessed mail.
  2. Parses text, HTML, and attachments.
  3. Deduplicates by message-id + preview (so the same mail isn't ingested twice).
  4. Creates a conversation keyed by the sender's email address.
  5. Uploads attachments to storage.
  6. Marks the mail as processed.

How "processed" is tracked

The poller prefers a custom IMAP keyword $HelpStackProcessed, which leaves the \Seen flag untouched (so the message still looks unread in other clients). If the server does not support custom keywords, it falls back to the \Seen flag.

Recommendation: use a dedicated mailbox for the channel. If the server doesn't support custom keywords, HelpStack relies on \Seen to avoid re-ingesting mail — sharing the mailbox with a human reader (who marks things read/unread) can cause missed or duplicated ingestion.

Threading

Threading information is stored on the conversation metadata so replies thread correctly:

Metadata fieldSource
emailSubjectSubject of the inbound mail
emailMessageIdMessage-ID header
emailReferencesReferences / In-Reply-To chain

Running the poller

npm run email:poll

Run this worker alongside the app (and the message queue worker) so inbound email is ingested. In production it polls roughly every 60s; in development roughly every 10s.

Environment tunables#

VariableDefaultPurpose
EMAIL_MAX_BODY_BYTES~100 KBMax body size ingested; longer bodies are truncated
EMAIL_MIN_ATTACHMENT_BYTES~1 KBAttachments smaller than this are skipped (filters tracking pixels)
EMAIL_MAX_AGE_DAYS5Ignore mail older than this many days
IMAP_ALLOW_SELF_SIGNEDDev only — accept self-signed IMAP TLS certs

Encryption#

Each channel's IMAP/SMTP credentials are stored encrypted with AES-256-GCM. The dashboard provides a Test button that validates the configured credentials (connects and authenticates) before you save/rely on them.

Verified credential fields for an email channel (credentials):

host, port, username, password, from,
secure?, imapHost?, imapPort?, imapSecure?, imapAllowSelfSigned?

Outbound: SMTP via nodemailer#

Replies are sent over SMTP using nodemailer with the channel's own SMTP credentials: host, port, secure, user, pass, and from. Outbound sending is handled by the message queue's senders.

Channel SMTP vs. system notification SMTP

These are two separate mail paths — don't confuse them:

Channel SMTPSystem notification SMTP
PurposeCustomer replies on an EMAIL channelPlatform/system notification emails
CredentialsPer-channel, encryptedEnv vars
Configured viaChannel settingsSMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_SECURE, SMTP_FROM
Default fromThe channel's fromnoreply@helpstack.eu

System notification emails (e.g. account/system messages) always use the env-configured SMTP and the noreply@helpstack.eu default sender, independent of any channel.