Dashboard System health: OK
Sending health and audience at a glance — last 30 days. The health badge is one derived value from GET /orgs/{org}/health; raw CloudWatch alarms deliberately stay out of a marketing console.
Sends & engagement
Not yet builtDeliverability · latest edition
HealthyRecent campaigns
View all →Newsletters
Each newsletter is a list with its own opt-in policy, from-address and compliance footer.
| Newsletter | Opt-in | Subscribers | From address | Cadence | Access | Signups | |
|---|---|---|---|---|---|---|---|
| The Morning Ledger | Double opt-in | 97,408 | ledger@northwindtimes.example | Daily · 6am ET | Free | Open | |
| Market Signal | Double opt-in | 8,410 | signal@northwindtimes.example | Weekly | Paid | Open | |
| Field Notes | Double opt-in | 54,880 | notes@northwindtimes.example | Weekly · Thu | Free | Open | |
| Weekend Long Read | Double opt-in | 61,300 | read@northwindtimes.example | Weekly · Sat | Free | Open | |
| Product Dispatch | Single opt-in | 12,940 | product@northwindtimes.example | Ad-hoc | Free | Closed |
Subscribers
The addressium subscriber record is the primary identity — sub is a UUID minted at signup. A Cognito pool is optional and link-only, joined on externalId; no pool has to exist anywhere to run a list. Click a row to view, manually unsubscribe, or suppress.
| Subscriber | Status | Entitlement | Lists | Last click | sub |
|---|---|---|---|---|---|
JR Jordan Reyes jordan.reyes@example.com | Confirmed | Paid | 3 | 2h ago | a1f3…9c2 |
MO Mei Okafor mei@okafor.example | Confirmed | Free | 2 | 1d ago | 7be0…14a |
TS Tomás Silva tsilva@silva.example | Pending | Free | 1 | — | c40d…8f1 |
AH Aisha Hassan aisha.h@example.com | Confirmed | Paid | 4 | 5h ago | 2d99…30b |
DK Daniel Kim dan.kim@example.com | Unsubscribed | Free | 0 | 12d ago | 5aa1…7e4 |
Segments
Saved predicates over lists, status and attributes. The engine is chosen at deploy time: the v1 GSI engine (shipped default) needs a base list and cannot resolve engagement recency. The opt-in OpenSearch mirror lifts the base-list requirement; engagement recency is not resolvable on either engine today — see the builder.
| Segment | Size | Basis | Engine |
|---|---|---|---|
| Paid subscribers | 14,860 | list + entitlement = paid | GSI |
| NYC metro | 9,410 | list + attribute · city | GSI |
| Launch test cohort | 42 | explicit · 42 subscriber ids | any |
Lapsed — no open 90d
Campaigns
One-offs and recurring series. Reporting is per campaign; recurring sends run on EventBridge Scheduler, and every schedule carries a lifecycle record that is the source of truth for whether it may fire.
| Campaign | Type | Status | Audience | Open | Click | When | |
|---|---|---|---|---|---|---|---|
| The Morning Ledger — Jul 20 | Ongoing · Daily | Sent | 96,204 | 44.1% | 8.3% | 2h ago | |
| Market Signal — Weekly Brief | Ongoing · Weekly | Sent | 8,410 | 61.7% | 14.2% | Yesterday | |
| Weekend Long Read — Jul 20 | Ongoing · Weekly | Scheduled | 61,300 | — | — | Sat 8:00 ET | |
| Field Notes — Jul 17 | Ongoing · Weekly | Paused | 54,880 | — | — | Held 2d | |
| Product Dispatch — v4 launch | One-off | Draft | — | — | — | 3d ago |
A CampaignSeries type exists but no code writes one. There is no aggregate across editions — edition count, average open/click, trend — and no reschedule and no resend. Reporting today is per campaign, on the report screen.
New campaign
Compose → choose audience → review & send.
- Newsletter
- The Morning Ledger
- Audience
- All confirmed · 97,408
- Suppressed
- 1,204 (bounces + complaints) → 96,204 mailable
- From
- ledger@northwindtimes.example
- Unsubscribe
- One-click (RFC 8058)
- Send
- Now — placed 5 minutes out · 340 msg/s
Templates
Three authoring modes — pick the right one per team. All render through the same responsive pipeline, and List-Unsubscribe headers (incl. RFC 8058 one-click) are set on every message.
Drag-and-drop blocks (GrapesJS → MJML). For editors & ad reps building polished sends without touching code. Open →
Responsive markup + merge tags with live preview. Full control over layout.
Paste advertiser-supplied HTML as-is. Hard-sanitized at save and schedule time; the footer merge value is yours to include.
<mj-section>
<mj-column>
<mj-text font-size="17px">
Good morning, {{ first_name }}.
</mj-text>
<mj-button href="{{ editorial_url }}">
Read today’s lead
</mj-button>
<mj-text>{{ compliance_footer }}</mj-text>
</mj-column>
</mj-section>
Good morning, Jordan.
The one chart that explains this week’s market — and three things to watch before Monday.
Northwind Times · 123 Main Street, Anytown, USA · Unsubscribe
Automations
Linear drip sequences on Step Functions — waits and sends, in order. Two triggers exist: signup (a completed double opt-in) and manual. No conditional splits, branches, holdouts, goal exits or re-entry rules.
| Sequence | Trigger | Steps | Consent gate |
|---|---|---|---|
| Welcome series | signup The Morning Ledger | 3 | per step |
| Field Notes primer | signup Field Notes | 2 | per step |
| Paid onboarding | manual enrolled by hand | 4 | per step + entitlement |
The sweep is built and runs: one weekly EventBridge rule (Mondays 04:00 UTC) invokes a dispatcher that finds the orgs with reengagement.enabled and sweeps each in checkpointed pages, resuming where it stopped. What is not yet built is any console surface for it — no route writes Organization.reengagement, so the policy below is deployment configuration, not a screen. The values shown are the shipped defaults.
- enabled
- false per-org opt-in, never deployment-wide
- listId
- required once enabled · no default
- coldAfterDays
- 180
- steps
- 3 win-back emails
- stepIntervalDays
- 7
Analytics — The Morning Ledger, Jul 20
Per-campaign counters, deliverability rates and the per-link click table. Magic-link tokens are redacted before a click is stored.
The one chart that explains the market
Good morning, Jordan. Futures slipped overnight after the jobs print. Here’s the chart everyone’s sharing3,204 and why it matters.
In markets: rates642, energy418, and a deal to watch1,090.
Northwind Times · 123 Main Street, Anytown, USA · Unsubscribe54
Top links
editorial only| Link | Clicks | CTR |
|---|---|---|
| the chart everyone’s sharing | 3,204 | 3.4% |
| a deal to watch | 1,090 | 1.1% |
| rates | 642 | 0.7% |
| energy | 418 | 0.4% |
| Unsubscribe | 54 | 0.1% |
All links
editorial only| Link | Clicks | Unique | CTR |
|---|---|---|---|
| the chart everyone’s sharing | 3,204 | 2,981 | 3.4% |
| a deal to watch | 1,090 | 1,004 | 1.1% |
| rates | 642 | 598 | 0.7% |
| energy | 418 | 402 | 0.4% |
| Unsubscribe | 54 | 54 | 0.1% |
Every other SES event
own counter eachSettings
Deployment configuration for this addressium account.
Sending domains
Built| Domain | On the org record | SES identity | Config set |
|---|---|---|---|
| northwindtimes.example | domains[0] | Created at provisioning | Created at provisioning |
| news.northwindtimes.example | domains[1] | Created at provisioning | Created at provisioning |
Verification & quota readout
Not yet built| Domain | DKIM | SPF | DMARC | Tracking |
|---|---|---|---|---|
| northwindtimes.example | Verified | Pass | p=reject | click.northwindtimes.example |
| news.northwindtimes.example | Verified | Pass | p=none | click.northwindtimes.example |
- Signing
- Asymmetric · ES256 (KMS)
- JWKS
- /.well-known/jwks.json
- Placement
- URL fragment #tok=
- Scope
- content:read
- TTL
- 14 days
- Redemption
- Reusable · stateless
- Last sync
- 4 min ago · 212 updated
- Values
- free paid
Point alerts at an SNS topic — fan out to email, SMS, Slack, PagerDuty, or a Lambda.
Where an AWS account very likely already runs something, addressium consumes it by configuration rather than creating a competing copy. Deliverability alerts above are for the person running the lists; these are for the on-call.
- opsAlertTopicArn
- arn:aws:sns:us-east-1:…:oncall
- apiWebAclArn
- not set
- cloudfrontWebAclArn
- not set
Export or erase a person by email. Erasure anonymizes the profile in place (email → erased:<sub>, attributes cleared, status suppressed), unsubscribes every subscription, and deletes the external-id pointer, the email reservation, the entitlement record and every event row naming them. It returns an ErasureReport of what it reached, not a bare true.
- Consent capture
- timestamp · IP · source URL
- auditRetentionYears
- 7 · Object Lock, set-once
- analyticsEventRetentionDays
- 730 · opt-in lake only
- Operational event log
- append-only · no TTL
- Right to be forgotten
- Automated
- Data residency
- us-east-1 · your account
Admin team
Staff in the separate admin user pool. Each member has a role, scoped to one or more organizations.
| Member | Role | Org access | MFA | Last active |
|---|---|---|---|---|
DA Dana Alvarez dana@northwindtimes.example | Developer Admin | All orgs | TOTP | Now |
PW Priya Walsh priya@northwindtimes.example | Editor | Northwind · Lakeside | TOTP | 2m ago |
RB Ruben Ba ruben@northwindtimes.example | Analyst (Sales) | All orgs | TOTP | 3d ago |
MC Marco Cruz marco@northwindtimes.example | Support | Northwind | Pending | — |
Feeds Not yet built
Pull articles from RSS / Atom / JSON feeds to auto-build campaigns. Feed fields map to merge tags.
| Feed | Maps to | Last pulled | Items | Status |
|---|---|---|---|---|
Homepage RSS northwindtimes.example/feed.xml | The Morning Ledger | 6 min ago | 28 | Active |
Markets JSON api…/markets.json | Market Signal | 14 min ago | 12 | Active |
Features Atom northwindtimes.example/features.atom | Field Notes | 1 h ago | 6 | Paused |
- <title>
- → {{article_title}}
- <link>
- → {{editorial_url}} + token
- <description>
- → {{article_excerpt}}
- media:thumb
- → {{article_image}}
Merge tags Not yet built
In-email replacement variables and where each value comes from at send time.
| Tag | Source | Scope | Example | Fallback |
|---|---|---|---|---|
| {{first_name}} | Profile attribute | per-recipient | Jordan | there |
| {{editorial_url}} | Feed link + magic token | token | …/article#tok=… | — |
| {{entitlement}} | Profile · synced | token claim | paid | free |
| {{article_title}} | Feed field | per-campaign | The one chart… | — |
| {{unsubscribe_url}} | System | per-recipient | …/u/… | — |
| {{compliance_footer}} | System · reserved merge value | per-campaign | Northwind Times · 123 Main Street… | — |
| {{physical_address}} | System · list address | per-campaign | 123 Main Street, Anytown, USA | — |
Ad tags Not yet built
LiveIntent ad HTML per slot, inserted as-is — never tokenized or click-tracked.
Slots in this template
7 slots| Slot | Position | This edition |
|---|---|---|
| {{ad_top}} | Above the fold | Filled |
| {{ad_inline_1}} | After story 1 | Filled |
| {{ad_inline_2}} | After story 2 | Filled |
| {{ad_inline_3}} | After story 3 | Filled |
| {{ad_native}} | Native unit | Filled |
| {{ad_sidebar}} | Sidebar | Empty |
| {{ad_footer}} | Below footer | Empty |
Identity & pools
Magic-link signing, plus the optional Cognito pools an org can link. Held on the organization record; scoped to Northwind Times. Written at provisioning time and read-only here — no route updates an organization.
Data & exports
Migrate a subscriber base in, take it back out again — scoped to this organization. Run the SES suppression import before this.
both shapes Pinpoint hands out — dotted-column CSV, or an export job’s .jsonl.gz
- Attributes.SD_Ledger
- → The Morning Ledger
- Attributes.SD_Signal
- → Market Signal
- Attributes.companyname
- → attribute, not a list
- OptOut · EndpointStatus
- never mailable
Import batches
GET /import/batches| Batch id | Rows | Status | Finished |
|---|---|---|---|
| ledger-migration-0720 | 16,502 | running | — |
| ledger-dryrun-0719 | 16,502 | completed | Yesterday 14:08 |
| signal-0716 | 418 | completed | 4 days ago · 12 rows reported |
| courier-0712 | 0 | failed | 8 days ago · not a CSV or a gzip |
Jordan Reyes Paid
jordan.reyes@example.com
- Subscriber sub
- a1f3b8c0-…-9c2
- externalId
- 4c02…b71 · linked pool
- Organization
- Northwind Times
- Status
- Confirmed
- Entitlement
- paid · asof 4 min ago
- Source
- Signup form · homepage
- Consent
- 2025-11-04 · IP 73.x · double opt-in
- Suppression
- None
List subscriptions
Recent activity
Not yet builtSuppression
Addresses that are never sent to. Hard bounces and complaints are added automatically.
POST /orgs/{org}/import/suppression reads the SES account suppression list — the one SES maintains itself from hard bounces and complaints. This is the half of a migration nothing else can reconstruct: subscriber records can be re-exported from the source at any time, but “this address hard-bounced two years ago” exists only here. Skip it and the first campaign after the migration mails every one of those addresses, straight into the rates the deliverability halt exists to catch, on day one. Run it before the subscriber import.
- Scope written
- Global bounces and complaints — they threaten the reputation every org shares
- Unknown reasons
- reported by address, never guessed — coercing an unread value into bounce would invent a permanent global suppression
- Timestamps
- SES’s own kept as addedAt — it is the evidence; stamping it with the import date destroys exactly that
- Direction
- read-only — nothing is written back to your account list
- Capability
- suppression:manage deliberately not the subscribers:manage its sibling import routes use
How the do-not-send list is shared across your organizations. Set once per deployment; override per org if needed.
| Address | Reason | Scope | Added |
|---|---|---|---|
| bounce@olddomain.example | Hard bounce | Global | 2h ago |
| angry@example.com | Complaint | Global | Yesterday |
| dan.kim@example.com | Unsubscribe | Northwind Times | 12d ago |
| gone@olddomain.example | Hard bounce imported from SES | Global | 2023-04-11 |
| quiet@example.com | Inactive sunset sweep | Northwind Times | 3 wk ago |
| test@spamtrap.example | Manual | Global | 1 mo ago |
Organizations
Each organization is an isolated silo. Silos are created here — POST /orgs provisions one and links a pool if you name it. There is no org-update route, so the pool link, the domain and the identity config are set at provisioning time and read-only afterwards; the setup checklist below is derived, not editable.
| Organization | Domain | Subscriber pool ID | Time zone | Sending IP | Suppression | Setup |
|---|---|---|---|---|---|---|
| Northwind Times | northwindtimes.example | us-east-1_Smt7Rp4Wq | America/Denver | Shared | Hybrid | Verified |
| Lakeside Ledger | lakesideledger.example | us-east-1_Lakeside3Kp9x | America/Denver | Dedicated | Hybrid | Verified |
| Copperline Courier | copperlinecourier.example | — magic links off | America/Denver | Shared | Per-org | DMARC pending |
| Northwind Staging DEV | devnorthwindtimes.example | — | America/Denver | Shared | Per-org | Verified |
Computed live from the org’s config; the required steps flip setupComplete.
A dev org is a full silo — its own SES identity, config set and reputation — running on the exact same workflows and Lambdas; nothing extra is deployed. The flag surfaces a DEV badge so a test publication is never mistaken for a live one.
API & webhooks Not yet built
What exists today are two inbound webhooks, HMAC-signed and configured at deploy time. There is no API-key issuance and no outbound delivery.
Inbound webhooks
Built| Route | Purpose | Verification | Last received |
|---|---|---|---|
| POST /webhooks/entitlement | Billing system sets free / paid | HMAC x-addressium-signature | 4 min ago |
| POST /webhooks/identity | Upsert / delete matched on externalId; delete routes through GDPR erase | HMAC x-addressium-signature | 1h ago |
API keys
Not yet built| Name | Key | Scope | Last used |
|---|---|---|---|
| Billing entitlement sync | sk_live_9f2c…a1 | entitlement:write | — |
| CMS integration | sk_live_2b7d…c4 | subscribers:manage | — |
Usage & cost
addressium is free software — this is your AWS spend, attributed per organization for chargeback across publications.
By organization
| Organization | Emails (mo) | SES cost | Dedicated IP | Storage (S3) | Est. total |
|---|---|---|---|---|---|
| Northwind Times | 268,400 | $26.84 | — | 1.2 GB | $27.90 |
| Lakeside Ledger | 112,900 | $11.29 | $24.95/mo | 0.6 GB | $37.30 |
| Copperline Courier | 30,700 | $3.07 | — | 0.3 GB | $4.10 |
| Northwind Staging DEV | 340 | $0.03 | — | 0.0 GB | $1.00 |
| Deployment baseline CloudWatch alarms · data key · 2 secrets | — | — | — | — | $4.80 |
| Total | 412,340 | $41.23 | $24.95 | 2.1 GB | $75.10 |
Visual builder
Drag-and-drop email builder (GrapesJS → MJML). Outputs the same responsive MJML as hand-authored templates. The compliance footer is a reserved merge value your template has to place — it is not added for you.
Good morning, {{first_name}}
Roles & access
What each admin role can do. Four roles, fixed — assign one per member and scope it to specific organizations. Enforcement is server-side via Cedar; the console hides controls only as a convenience, never as the security boundary.
| Capability | Developer Admin | Editor | Analyst (Sales) | Support |
|---|---|---|---|---|
| View reports & analytics | ✓ | ✓ | ✓ read-only | ✓ |
| Schedule sends · pause / resume / archive | ✓ | ✓ | — | — |
| Create / edit / send campaigns | ✓ | ✓ | — | — |
| Manage templates & segments | ✓ | ✓ | — | — |
| Manage individual subscribers | ✓ | ✓ | — | ✓ |
| Manually unsubscribe someone | ✓ | ✓ | — | ✓ |
| Subscriber-site branding & presentation | ✓ | ✓ | — | — |
| Import subscribers (CSV / JSONL) | ✓ | ✓ | — | ✓ |
| Delete contacts · erase a person · bulk export | ✓ | — | — | — |
| Close / reopen newsletters | ✓ | — | — | — |
| Manage suppression & alerts · import the SES account list | ✓ | — | — | — |
| Read the audit log | ✓ | — | — | — |
| Identity, pools, organizations | ✓ | — | — | — |
| Manage team & roles | ✓ | — | — | — |
Branding Built
How the subscriber site looks — the opt-in directory, the confirm page, the preference centre and the unsubscribe page. Per org, no rebuild: POST /orgs/branding behind branding:manage, which the Editor role holds. Reading it is public — the subscriber site fetches its own theme.
Per-list presentation
POST /lists/presentationAudit log
Every privileged admin action, immutable — into an S3 bucket under Object Lock, read through GET /orgs/{org}/audit behind team:manage. Filter by member, organization or action type.
| When | Member | Action | Org |
|---|---|---|---|
| 2m ago | Priya Walsh | drip.enroll Enrolled jordan.reyes@example.com in “Paid onboarding” | Northwind Times |
| 1h ago | Dana Alvarez | alerts.update Alert thresholds — complaint warn 0.1% / halt 0.3% | Northwind Times |
| 3h ago | Priya Walsh | subscription.unsubscribed Manually unsubscribed dan.kim@example.com from The Morning Ledger | Northwind Times |
| 4h ago | Dana Alvarez | privacy.erase Erased leaving@example.com — events=214 subs=3 | Northwind Times |
| Yesterday | Dana Alvarez | subscribers.export Exported 97,408 subscribers (CSV) — key, bytes recorded | Northwind Times |
| 2d ago | Dana Alvarez | orgs.create Provisioned organization “Copperline Courier” | GLOBAL |
| 3d ago | Priya Walsh | import.run Import batch signal-0716 — 418 rows, 12 reported | Northwind Times |
| 5d ago | Dana Alvarez | suppression.import Imported SES account suppression list — 1,204 addresses, 3 reasons unmapped | Northwind Times |