Notifications¶
Two notification channels are integrated: Slack (operational / status alerts to a fixed channel) and SendGrid (transactional email via dynamic templates).
Slack¶
Used for operational signals from background jobs — color-validation results, social-token expiration alerts, generation completion / errors. Not used for end-user notifications.
Package¶
slack (npm). Imported per-file:
Configuration¶
| Setting | Value | Where |
|---|---|---|
| Bot token | xoxb-3144030948916-... (hardcoded) |
Inline in each consuming job file |
| Channel ID | C05TS9AHBH6 (hardcoded) |
Inline |
⚠️ The Slack token and channel are hardcoded in source. They're not in
.env. Rotating the token requires a code change in every consuming file (see Security → Secrets).
Posting messages¶
await bot.chat.postMessage({
channel: 'C05TS9AHBH6',
text: `[job_color_check] account ${accountId}: contrast adjusted`,
});
There is no shared Slack helper — each job inlines the import, the token, and the channel constant.
Where it fires¶
| File | Trigger |
|---|---|
job_color_check.js |
Color contrast validation result on a generated design |
job_auto_disconnect.js |
Social token expired / account requires reconnect |
| Various generation jobs | Job-level errors and completion reports |
Operational notes¶
- No retries — if Slack is unreachable, the message is silently dropped (the surrounding
try/catchtypically logs toconsole.errorand moves on). - No rate limiting — high-traffic jobs can spam the channel.
- No structured payload — messages are plain text. No Slack Blocks, no buttons, no thread replies.
When adding a new Slack notification¶
- Don't introduce another hardcoded token. Either move the token to
.envfirst or add it to a shared helper. - Wrap in a try/catch — never let a failed Slack call kill a job.
- Include enough context to debug from the message alone (account ID, job name, timestamp).
SendGrid (Email)¶
All transactional email goes through SendGrid Dynamic Templates — no raw HTML email is generated in code.
Package¶
Configuration¶
| Env var | Purpose |
|---|---|
SENDGRID_API_KEY |
API key, loaded via conf.js |
Template IDs are stored as constants in helper/constants.js.
Delivery model¶
Emails are not sent inline from request handlers. Instead, they are enqueued in tEmailSchedule and processed by job_send_mail.js:
[ Route handler ] [ tEmailSchedule ] [ job_send_mail.js ]
│ │ │
│ INSERT INTO tEmailSchedule │ │
│ (TemplateId, Email, DetailedInfo, │ │
│ Scheduled_time, Status='Inserted') │ │
├─────────────────────────────────────────────►│ │
│ │ (every 1 minute via cron) │
│ │◄──────────────────────────────────────│ poll Status='Inserted'
│ │ │ AND TemplateId='d-bf7b...'
│ │ ┌────────────────────┤ mailService.send()
│ │ │ │
│ │ ▼ │
│ │ SendGrid API │
│ │ │ │
│ │ ▼ │
│ │ Recipient inbox │
│ │ │
│ │ UPDATE Status='Delivered' or 'Pending'│
│ │◄──────────────────────────────────────│
This decoupling means:
- A request handler returns immediately after enqueueing.
- Email failures don't break user-facing flows.
- Scheduled / delayed emails are trivially supported via Scheduled_time.
job_send_mail.js¶
- PM2-managed, runs every ~1 minute (
*/1 * * * *) - Reads
tEmailSchedule WHERE Status = 'Inserted' AND TemplateId = 'd-bf7b8ec288304eefba4039d08ccf0cbb'(see ⚠ note below) - Calls SendGrid with
{ templateId, to, dynamic_template_data } - On success →
Status = 'Delivered'. On failure →Status = 'Pending'.
⚠ Two corrections vs. the previous version of this doc:
1. Status values are strings, not integers. The previous doc described a numeric
0 → 1 → 2state machine. The actualtEmailSchedule.Statuscolumn isvarchar(50)and uses string values:'Inserted'(pending),'Delivered'(sent),'Pending'(failed — confusingly named). Anyone querying with integer values will match nothing.2.
job_send_mail.jsfilters on a single TemplateId. The actual SELECT injob_send_mail.js:20filters to a specific TemplateId ('d-bf7b8ec288304eefba4039d08ccf0cbb', the admin-confirmation template). It does not poll all pending emails. This means: - Either there are other email-sending workers we haven't fully traced (look for additional jobs pollingtEmailSchedulewith different TemplateId filters) - Or other templates accumulate forever intEmailSchedulewithStatus = 'Inserted'This needs investigation. If the second case is true, the email queue has a silent backlog and other email types are not being delivered through this job. If the first case is true, document the other workers here and flag the architecture as having multiple email processors operating on disjoint TemplateId subsets.
Template catalog¶
The constants are defined in helper/constants.js. Categorized:
Plan welcome / confirmation¶
| Template ID | Plan |
|---|---|
d-47c22927d5ec4dc082b6ae025d35ae8a |
Accelerate (monthly) |
d-85b484bad1ef47b2b8bb6cccde7e6f97 |
Launch (monthly) |
d-85cdaa9925f942aab0c5df28a72a8a6e |
Supercharge (monthly) |
d-5b4ae3f552d74d4ea3ba76b66d4adccf |
Accelerate (yearly) |
d-c3ffb757a70e420bae4824409085dd69 |
Launch (yearly) |
d-6046f1f1dde94a6bbc03a1c9faff91f7 |
Supercharge (yearly) |
System / lifecycle¶
| Template ID | Trigger |
|---|---|
d-1cea4e80209a49dca90451333ea578ad |
Token expired / auto-disconnect |
d-c9b699718a224a7ab4ae87e889fc2c80 |
Reconnection reminder |
d-2a5b752104c44c67ac269d1fa619392d |
Card expired |
d-bf7b8ec288304eefba4039d08ccf0cbb |
Admin confirmation |
d-be6b2fe487564f3e95069a66106c12c7 |
Admin approval |
d-96e697a642344c5b9218e02092143720 |
MFA code sent |
d-686730f97d6347b2be1e55a112c878ec |
Account verification |
d-abb8180b15f5499892fb1efaa9d59b04 |
Generation complete |
Template IDs are environment-agnostic at the API level — all environments hit the same SendGrid templates. Be careful when changing template content; non-prod traffic is using the same templates as prod.
Major callers (enqueue path)¶
These places insert into tEmailSchedule:
routes/auth.js— registration, password reset, MFA, plan changesroutes/routes.js— generation completion, account verificationjob_new_auto_disconnect.js— token expiration / reconnection nudgesjob_generation_complete.js— long-running content generation finished
Operational notes¶
- No HTML fallback — if a SendGrid template is broken, the email fails entirely. There's no inline HTML backup.
- No bounce handling in code — bounces / spam reports must be handled in the SendGrid dashboard.
- No localization at the queue layer — the language must be encoded into the chosen
templateId(one template per language) or via dynamic data fields. - Single API key — all environments share one key unless
.envis per-environment. Verify in your deployment.
Common Patterns¶
Adding a new email type¶
- Create the dynamic template in SendGrid; copy the
d-...ID. - Add a constant in
helper/constants.js: - Enqueue from your handler / job:
job_send_mail.jspicks it up on the next tick. No code change needed in the worker.
Adding a new Slack alert¶
- Move the token to
.env(SLACK_BOT_TOKEN) if you haven't already — don't perpetuate the hardcoded pattern. - Wrap in
try/catch. Never let a Slack call abort the calling job. - Use a descriptive prefix:
[<job_name>] <accountId>: <message>.
Related¶
- Configuration Reference —
SENDGRID_API_KEY(and where to addSLACK_BOT_TOKEN) - Jobs Inventory —
job_send_mail.js,job_auto_disconnect.js,job_color_check.js,job_generation_complete.js - Data Model —
tEmailScheduleschema - Security — secrets handling concerns including the hardcoded Slack token