• v0.15.0 cf5ba3bcaf

    vmanne-poc released this 2026-10-26 09:14:37 -04:00 | 0 commits to main since this release

    Phase 13: A/B testing, project API keys, API sending providers, and MJML. PR #69.

    Upgrading: back up the database first. Two additive migrations run on start (ab_testing, api_keys). New runtime dependency: mjml 5.4.1 (exact, MIT).

    Added

    • A/B testing (#20): variants B–D (subject, preview text, content), a 1–50 % test group, automatic winner by opens or clicks after a wait, or a manual pick; the rest get the winner. Per-variant report section and variant test sends.
    • Project API keys (#29): created by owners in Project settings, shown once, stored hashed, revocable, per-key rate limit. Authorization: Bearer postak_… on the project's contacts, groups, segments, campaigns and read-only senders/templates routes.
    • API sending providers (#49): Amazon SES (API v2), SendGrid, Mailgun, and Postmark senders over HTTPS, no provider SDKs.
    • MJML (#50): MJML campaigns and templates, compiled in a sandboxed worker with size and time limits.

    Changed

    • Campaign and template routes accept JSON bodies up to 4 MB with a Bearer header; nginx limits writes to them to 5 per second per address.
    • Temporary MJML compiler failures answer 503 and are retried during delivery.
    • The visual email builder (#19) moved to Phase 14 (v0.16.0).

    Tests

    Backend unit 366/366, e2e 67/68 (known-flaky sign-in rate-limit test), frontend 159/159. Smoke 55/55.

    Not verified: real delivery through SES API, SendGrid, Mailgun or Postmark (no credentials), and MJML rendering in real mail clients.

    See CHANGELOG.md for details.

    Downloads
  • v0.14.0 7424035a48

    vmanne-poc released this 2026-10-19 12:02:39 -04:00 | 15 commits to main since this release

    Upgrading: back up the database first. Two migrations run on start:
    contact_groups (additive: groups, memberships, and form group links) and
    campaign_audience (campaign include/exclude targets and the audience
    recorded at send time). The second one moves each campaign's segmentId
    into the new audience (unsent campaigns) or the recorded audience (sent
    campaigns) and then drops the segmentId column.

    Added

    • Groups (#55): hand-managed lists of contacts per project, with a name
      (unique ignoring case) and description, member counts, and a Groups page
      and group page with paged, searchable members. Add or remove contacts by
      id (up to 1,000) or everyone matching a contacts filter (up to 100,000),
      rate-limited by POSTAK_RATE_LIMIT_BULK. At most 500 groups per project.
    • Contact profile (#56): the contact page shows the contact's groups as
      chips you can remove, adds them to an existing or new group, and lists the
      segments they match right now.
    • Contacts list (#57): a group filter (also on export), a Groups column,
      row and page selection, "Select all N contacts that match", and bulk
      "Add to group" / "Remove from group".
    • Import, export, and forms (#58): CSV import reads a groups column
      (names separated by |), can create missing groups, and can add everyone
      in the file to chosen groups. Exports gain a groups column. Sign-up forms
      can add new subscribers to groups (on confirmation for double opt-in).
    • Campaign audiences (#59): send to any of several groups and segments,
      leave out others, and see a live count. Everyone is emailed once.
      Confirmations spell out the audience, and the report shows what a
      campaign was sent to. Groups and segments used by unsent campaigns can't
      be deleted.
    • Segment group rule (#60): "Group is in / is not in" conditions. The
      builder's rule containers are now called blocks.
    • Phase 12 review fixes (#61): test emails use sample data (Jane Doe) by
      default, or a contact you pick, and say whose data was used. Duplicate
      keeps the web view setting (with a new address) and the audience.

    Changed

    • Contact and segment CSV exports have a groups column after
      subscribed_at. A custom data key named groups is no longer exported as
      its own column.
    • CSV columns named groups or group are now read as group names, not
      custom fields. Rename the column to keep it as a custom field.
    • For sent campaigns, segmentId is historical: it comes from the audience
      recorded at send time and may refer to a segment that has since been
      deleted.
    • segmentId on campaigns is deprecated in favour of groupIds,
      segmentIds, excludeGroupIds, and excludeSegmentIds. It is still
      accepted on create, update, and GET campaigns/audience.

    PR #62.

    Downloads
  • v0.13.0 d483b5282b

    vmanne-poc released this 2026-10-15 12:39:27 -04:00 | 21 commits to main since this release

    Upgrading: back up the database first. One additive migration (templates_and_campaign_editor) adds style, footer, and default settings to templates, and tracking, public link, and campaign data to campaigns. Deleting a template now clears it from sent campaigns instead of failing. Existing campaigns keep tracking on and their public link off. nginx now forwards /c/ (the public web view) to the backend.

    Added

    • Styled templates with a live preview (#43): Styled (Postak's responsive layout, Markdown content), HTML, and Plain text types. Style options for the page, content, headings, links, buttons, quotes, and footer, stored as validated settings. A Markdown footer (Postak adds an unsubscribe link if it's missing). Clone a template and mark one as the project default. Preview runs in a sandboxed iframe with no scripts.
    • Campaign editor (#45): new and edit pages with sender, recipients (live count), subject, Plain text / Markdown / HTML editor, and template, plus a side-by-side preview for a chosen contact. Send a test email to up to 5 addresses (marked [Test], not tracked or counted). Duplicate any campaign, delete drafts, and confirm with the recipient count before sending or scheduling.
    • Preheader, personalisation, and campaign data (#46): the preview text becomes the hidden preheader (up to 150 characters). {{ contact.data.<key> }}, {{ campaign.data.<key> }}, and | default: "..." fallbacks. Unknown placeholders render as nothing and show a warning in the preview.
    • Tracking options and a public web view (#47): turn open/click tracking off per campaign (no pixel, no rewritten links). An optional public link serves a sent campaign at /c/<id> with neutral contact data and no tracking, and {{ web_view_url }} links to it.
    • Detailed campaign report (#48): clicks and unique clickers per link, a paged recipients table filterable by status with CSV export, opens and clicks over time (hourly for 48 hours, then daily) in an accessible chart with a table fallback, and a 14-day trend on the project overview.

    Fixed

    • Template type is respected (#44): Markdown bodies are converted first and then placed in the template layout, so HTML templates are no longer run through the Markdown parser.
    • Deleting a used template (#44) returns 409 when unsent campaigns use it, and clears it from sent campaigns, instead of a 500.

    Security

    • Contact and campaign data values are HTML-escaped in HTML bodies. The placeholder language is a small subset that can't run code.
    • The web view sends Content-Security-Policy: sandbox; default-src 'none' (images and inline styles only), nosniff, no-referrer, and no-store, and never shows recipient details.
    • Sent campaigns can't have their content edited.

    Verified

    • Backend: 101 unit and 55 end-to-end tests; migrations on an adopted legacy database and a fresh one. Frontend: 87 tests.
    • Live smoke through nginx and Mailpit: 51 of 51 checks passed (styled test send, personalised delivery, tracking, web view and its headers, report endpoints and CSV, and every new page rendered in headless Edge with no script errors).

    PR #54.

    Downloads
  • v0.12.0 2445086a7f

    vmanne-poc released this 2026-10-15 09:32:11 -04:00 | 27 commits to main since this release

    Merged in PR #53. Closes #39–#42.

    Upgrading: back up the database first. One migration gives each form a sender (existing forms get the project's oldest sender, as before). nginx now forwards /f/ to the backend. Set PUBLIC_URL to your public https address. Confirmation links in double opt-in emails now open a page with a Confirm subscription button.

    Added

    • Hosted sign-up page and embed code (#39): each form gets its own page with a shareable link, an iframe snippet and a plain HTML snippet. You can show a thank-you message or redirect to your own page, and choose whether other websites may use the form.
    • Form editor (#40) with a live preview:
      • heading, intro, button label and fine print
      • fields: email, names and your own custom fields, each required or optional, in any order
      • colours, font and corner radius
    • Confirmation and welcome emails (#41):
      • Choose the sender, write the confirmation email, and pick where people go after confirming.
      • An optional welcome email includes an unsubscribe link.
      • A double opt-in form without a sender now says it can't accept sign-ups, instead of silently sending nothing.
    • Spam protection (#42):
      • A hidden honeypot field, and a minimum fill time on every hosted form.
      • An optional captcha that the visitor's browser solves invisibly, with no third-party service.
      • Rejected submissions look the same as real ones to bots.

    Security

    • Contact details are escaped in emails. A name submitted through a public form could add HTML or links to campaign emails. This is now fixed.
    • Confirmation needs a button press, so link scanners can't confirm subscriptions on someone's behalf.
    • Form pages use a strict Content Security Policy.
    • Confirmation emails are limited to one per person per form every 2 minutes.

    Verified

    • Tests: backend 72 unit and 51 end-to-end; frontend 67.
    • Live through nginx and Mailpit:
      • full double opt-in with a custom field
      • the welcome email and its unsubscribe link
      • spam dropped
      • captcha solved and replay refused
    • A real browser solved the captcha on the hosted page.

    Full details are in CHANGELOG.md.

    Downloads
  • v0.11.0 c0c123137d

    vmanne-poc released this 2026-10-15 08:51:52 -04:00 | 32 commits to main since this release

    Merged in PR #52. Closes #32–#38.

    Upgrading: back up the database first. The two new migrations only add to the schema.

    • SMTP senders now verify TLS certificates. A server with a self-signed certificate needs "Allow a self-signed certificate" ticked on its sender.
    • The backend port is now bound to 127.0.0.1. Reach the API through nginx on port 3000.

    Added

    • Contact pages (#32): details, custom data, external ID, campaigns received and activity, plus new, edit and delete.
    • Search and paging (#33): search by email, name or external ID, filter by status, pages of 25.
    • CSV import and export (#34):
      • The file is read in the browser, and you see how each column will be used before importing.
      • Choose whether to update contacts that already exist (matched by external ID, then email).
      • Rejected rows are reported and can be downloaded.
      • A template and instructions for Excel, LibreOffice and Google Sheets are included.
      • Import never changes a contact's status. Exports defuse cells that look like formulas.
    • Sender setup (#35): edit pages, reply-to, SMTP security (SSL/TLS, STARTTLS, none), username and password, self-signed option, and a test send to yourself. Passwords stay write-only.
    • Background sending (#36):
      • Sending returns right away and a worker delivers in the background.
      • Per-sender limits (per second, minute and hour) are honoured.
      • Temporary failures are retried with back-off.
      • The campaign list shows progress, and the report shows failures with the server's reason.
    • Segment editor (#37): OR groups of AND conditions, a JSON query, a live list of matching contacts, and a CSV download.

    Security

    • Rate limiting (#38): 10 requests per minute per client on sign-in, password reset, registration, form submissions and test emails; 600 per minute elsewhere. nginx sets the client address, so clients can't fake it.
    • TLS certificates are verified by default. From and Reply-To headers can no longer be broken by unusual names.

    Verified

    • Tests: backend 62 unit and 46 end-to-end; frontend 63. npm audit: 0 for both.
    • Live stack through nginx and Mailpit:
      • background send (request returned in 26 ms; 4 emails delivered with correct headers)
      • import, search and CSV exports
      • test email
      • login rate limiting, including with a spoofed X-Forwarded-For
      • a sender saved before this release still works

    Full details are in CHANGELOG.md.

    Downloads
  • v0.10.0 6b90ecba91

    vmanne-poc released this 2026-10-15 07:52:26 -04:00 | 41 commits to main since this release

    Merged in PR #51. Closes #25, #26, #27, #28, #30 and #31. No database migrations.

    Added

    • Project sidebar (#25): once a project is open, the sidebar lists its sections and marks the current one. A "← All projects" link goes back.
    • Project overview (#26): a card per section with a live figure, plus a "Get ready to send" checklist until the project has a sender, contacts and a first campaign. The contacts list is now at /projects/:id/contacts.
    • Project settings (#27): rename the project, or delete it after typing its name. Members see a read-only view.
    • System info (#28) for the administrator: version, database and migrations, and which features are on, with hints for turning on the ones that are off. It never shows secrets.

    Security

    • Sender credentials are no longer returned by the API (#30). Previously, any project member could read SMTP passwords and SES secret keys.
    • Single opt-in forms can't re-subscribe people (#31) who unsubscribed, bounced or complained.

    Verified

    • Tests: backend 53 unit and 39 end-to-end; frontend 39.
    • Live stack through the web server: rename, summary, system info with no secrets, senders without credentials, and the new pages.

    Full details are in CHANGELOG.md.

    Downloads
  • v0.9.0 4da052c114

    vmanne-poc released this 2026-10-15 01:03:41 -04:00 | 48 commits to main since this release

    Merged in PR #24. Closes #16, #23, #18 and #17.

    Upgrading: back up the database first. On first start, Postak adopts an existing v0.8.0 (or older) database into the new migration history. It refuses any schema change that would lose data.

    Changed

    • Versioned database migrations (#16). prisma db push --accept-data-loss is gone. Schema changes now ship as SQL migrations, applied with prisma migrate deploy on start. 0_init is the v0.8.0 baseline.

    Added

    • Account controls for the administrator (#23), on the Users page:
      • disable or enable an account, which ends its sessions immediately
      • set a temporary password, which the user must change after signing in
      • delete an account; projects where that user was the only owner go to the administrator
    • Forgot password by email (#23). Links are single-use, expire after 1 hour, and are throttled, and the response never reveals whether an account exists. Needs POSTAK_SMTP_URL, POSTAK_MAIL_FROM and POSTAK_APP_URL.
    • SES bounces and complaints (#17) via SNS at POST /api/webhooks/ses:
      • Every message's SNS signature is verified, and only topics listed in POSTAK_SES_SNS_TOPIC_ARNS are accepted. With none listed, the endpoint is off.
      • Hard bounces mark the contact BOUNCED, and complaints mark it COMPLAINED. Neither is mailed again.
      • Reports and daily snapshots now show real bounce and complaint numbers.
    • New migrations user_account_controls and contact_bounce_statuses. Both are additive.

    Testing

    • Frontend tests (#18): Vitest and Testing Library, run with docker compose run --rm --build -T frontend-test.
    • The backend test run now checks both migration paths and fails if a schema change has no migration.

    Security

    • Frontend toolchain upgraded: Vite 8, React Router 7.18 (fixes an open-redirect advisory), Node 22.
    • The backend also runs on Node 22, since Node 20 is end-of-life.
    • npm audit: 0 for the backend (production dependencies) and 0 for the frontend.

    Verified

    • Tests: backend 53 unit and 35 end-to-end; frontend 28.
    • Live stack: the existing v0.8.0 database was adopted with its data intact. Forgot password, temporary password, disable, and delete with project hand-over were all tested through the web server, with email going to Mailpit.
    • Not yet tested against real AWS SNS/SES. That path is covered by tests that use locally signed messages.

    Full details are in CHANGELOG.md.

    Downloads
  • v0.8.0 494c2052fc

    vmanne-poc released this 2026-10-14 23:56:29 -04:00 | 55 commits to main since this release

    Recommended upgrade: clears all known vulnerabilities in production dependencies and fixes missing unsubscribe links (#21).

    Merged via PR #22.

    Security

    • NestJS 10 → 11.2 (Express 5), @nestjs/config 4, @nestjs/schedule 6, @nestjs/jwt / @nestjs/passport 11, nodemailer 6 → 10, Jest 30. npm audit --omit=dev: 12 vulnerabilities (4 high) → 0 (#14).
    • Password change ends all other sessions; deleted users' tokens are rejected.

    Added

    • Account page: change password.
    • Users page (administrator): create accounts.
    • Members page: owner/member roles, add by email, change role, remove, leave (#15). Projects always keep one owner.

    Fixed

    • Emails without a template had no unsubscribe link (#21). Every email now has a footer link plus List-Unsubscribe / one-click List-Unsubscribe-Post headers.
    • Unsubscribes were counted as clicks.
    • Opening the unsubscribe link (GET) unsubscribed immediately, so link scanners could unsubscribe people. GET now shows a confirmation; POST unsubscribes.
    • Any member could delete a project; now owners only.
    • Emails stored lowercase; sign-in case-insensitive.

    Behavior changes

    • Integrations must POST /track/unsubscribe/:id to unsubscribe.
    • Documentation other than README, CHANGELOG, CONTRIBUTING, and LICENSE moved out of the code repository. The user guide is on the wiki.

    Upgrading

    • Rebuild: docker compose up -d --build. The schema change (User.tokenVersion) applies automatically; existing sessions stay valid.

    Tests: 45 unit + 29 end-to-end passing.

    Milestone: Phase 8 · Not included: password reset and account disable (#23).

    Downloads
  • v0.7.0 cb2fb17420

    vmanne-poc released this 2026-10-14 22:55:35 -04:00 | 61 commits to main since this release

    Recommended upgrade: fixes a security issue present in all earlier releases (#8).

    Added

    • Segment rule engine: whitelisted JSON rules (email, names, dates, custom data.<key>; all/any groups up to 3 levels) compiled to Prisma.
    • Segments API (CRUD + preview) and builder UI with live preview.
    • Campaign targeting by segment; schedule / unschedule endpoints and UI.
    • ProjectAccessGuard on every /projects/:projectId/... route.
    • End-to-end suite against PostgreSQL with a fake mailer (22 tests); 43 unit tests.
    • docker compose run --rm --build -T backend-test (resets only postak_test).
    • POSTAK_SCHEDULER_ENABLED, POSTAK_DISABLE_REGISTRATION.
    • User guide docs/user/segments.md.

    Fixed

    • #8 Project data was readable by any signed-in user.
    • #9 A campaign with a missing segment was sent to every contact.
    • #10 Campaigns could reference another project's sender, template, or segment.
    • #11 The same campaign could be sent twice.
    • #12 Sent campaigns were editable; PATCH bodies were not validated.
    • #13 Snapshot endpoint ignored the project and accepted invalid dates.

    Changed

    • npm ci with committed lockfiles; .dockerignore files.

    Known issues

    • npm audit --omit=dev: 12 vulnerabilities (4 high) in runtime dependencies. Tracked in #14 (Phase 8).

    Milestone: Phase 7 · Summary: #7

    Downloads
  • v0.6.0 16a89cf02b

    vmanne-poc released this 2026-10-14 22:16:18 -04:00 | 63 commits to main since this release

    Added

    • Templates UI: create, edit, delete.
    • Forms UI: single or double opt-in, shows each form's public submit endpoint.
    • Template picker in the campaign composer.
    • Jest + ts-jest unit tests (renderer, rate math): 14 passing.

    Changed

    • Rate calculation extracted to a tested rate() helper.
    • Production build excludes *.spec.ts.

    Security note: project-scoped routes do not check project membership. Fixed in v0.7.0 (#8).

    Milestone: Phase 6 · Summary: #6

    Downloads