Przeglądaj źródła

Plan HTA mass mailer with Access backend and add gitignore

master
Daniel Covington 2 dni temu
commit
30d9af38db
8 zmienionych plików z 410 dodań i 0 usunięć
  1. +34
    -0
      .gitignore
  2. +91
    -0
      .specs/features/mass-mailer/design.md
  3. +62
    -0
      .specs/features/mass-mailer/spec.md
  4. +49
    -0
      .specs/features/mass-mailer/test-plan.md
  5. +57
    -0
      .specs/project/PROJECT.md
  6. +12
    -0
      README.md
  7. +34
    -0
      config.example.ini
  8. +71
    -0
      tools/probe-mdb-providers.vbs

+ 34
- 0
.gitignore Wyświetl plik

@@ -0,0 +1,34 @@
# Local configuration and credentials (keep config.example.ini tracked)
/config.ini
/config.local.ini
*.secret.ini
*.secrets.ini
*.pfx
*.key

# Live Access databases and lock files
*.mdb
*.mde
*.accdb
*.accde
*.ldb
*.laccdb
*.mdw

# Runtime data and imported private templates
/data/
/templates/local/
/logs/
/backups/
/temp/
/tmp/

# Local output and editor/Windows artifacts
*.log
*.bak
*.tmp
*.old
~$*
Thumbs.db
Desktop.ini
.DS_Store

+ 91
- 0
.specs/features/mass-mailer/design.md Wyświetl plik

@@ -0,0 +1,91 @@
# MassMailer architecture (draft)

## Component map

```mermaid
flowchart LR
O[Owner] --> L[Launcher and database preflight]
L --> U[32-bit HTA UI]
U --> C[Campaign and contact services]
U --> D[In-app dispatch timer]
C --> M[(Access MDB)]
D --> M
D --> E[Eligibility and pacing policy]
E --> A[SMTP adapter]
A --> P[SMTP server]
```

The HTA owns campaign editing, preview, approval, pause, reporting, and dispatch. Sending is possible only while the HTA is open and the owner has explicitly started or resumed a campaign. Closing the window starts no further sends. On reopening, all campaigns remain paused until the owner explicitly resumes them. The timer is only a wake-up mechanism; durable database state enforces pacing. A single-instance guard prevents two HTAs from dispatching at once. Keep business rules separate from UI DOM code so they can be tested with a fake clock and SMTP adapter.

## Launch and provider check

The launcher reads `DB_PATH`, `DB_PROVIDER`, and `DB_BITNESS` from the INI, validates them, and starts the configured process architecture. On the current PC, the 32-bit host with `Microsoft.ACE.OLEDB.12.0` passed the temporary `.mdb` create/write/read probe; 64-bit hosts failed. The deployment launcher must therefore run the 32-bit `mshta.exe` on this PC. It must open the **configured** `.mdb` using the configured provider from the same architecture that will host the HTA, read `AppMeta.schemaVersion`, and fail with a clear error if the provider, file, permissions, or schema is wrong. The temporary diagnostic is useful on another PC but is not a substitute for this startup check. Do not silently fall back to another provider or create a new production database when opening fails.

## Campaign and dispatch flow

1. Import CSV contacts with source reference and collection date. Normalize addresses for case-insensitive matching while retaining the original value. Keep a global suppression list that re-import cannot clear.
2. Import a reusable HTML template and local images. Approval freezes the template, image bytes, recipient set, sender and reply identity, postal address, and subject in a campaign version. Create one `Delivery` per eligible address.
3. The owner explicitly starts or resumes a campaign. While the HTA remains open, the timer checks the global pause flag, campaign start instant, `nextAllowedAttemptUtc`, and pending deliveries. It attempts at most one recipient at a time. Recheck suppression immediately before contacting SMTP.
4. In a transaction, mark an `Attempt` as `Started` before the provider call. Record a definitive `Accepted` or `Failed` response afterward. If the outcome is unknown after a timeout or crash, mark it `Uncertain` on recovery and require manual reconciliation. SMTP acceptance does not prove inbox delivery.
5. Reserve the next account-wide slot after **every** attempt, including failures and retries: `nextAllowedAttemptUtc = max(now + interval, retryBackoffUntil)`. Missed slots never accumulate. On app close or restart, no new send begins and no automatic resume occurs.
6. Show the next eligible time, outcomes, errors, and opt-out review reminder. The owner checks the monitored reply mailbox daily and records opt-outs; the next dispatch checks suppression again.

A synchronous COM SMTP call may briefly block the HTA. The feasibility spike must measure that behavior, provide a timeout and visible sending state, and determine whether a small helper process is needed for responsiveness. Any helper must remain bound to the open HTA session and must stop accepting new work when it closes.

## Access MDB data model

| Entity | Essential fields |
| --- | --- |
| AppMeta | schemaVersion, createdAt, migratedAt |
| Contact | id, emailOriginal, emailKey, businessName, sourceReference, acquiredAt, country, eligibilityBasis |
| Suppression | emailKey, reason, recordedAt, evidenceReference |
| TemplateVersion | id, templateName, version, htmlContent, contentHash, importedAt, validationStatus |
| TemplateImage | id, templateVersionId, originalRelativePath, contentType, contentHash, snapshotBytes, contentId |
| Campaign | id, name, senderKey, fromSnapshot, replyToSnapshot, postalAddressSnapshot, subject, templateVersionId, status, startAtUtc, approvedAt |
| Delivery | id, campaignId, contactId, emailKey, status, attemptCount, lastAttemptAt, providerMessageId, lastErrorCode |
| Attempt | id, deliveryId, startedAtUtc, endedAtUtc, outcome, providerRequestId, sanitizedError |
| DispatchState | senderKey, lastAttemptAtUtc, nextAllowedAttemptUtc |
| OperationalState | id, globalPause, lastOptOutReviewAtUtc, lastConfigHash |
| ProtectedSecret | id, purpose, dpapiCiphertext, createdAt, rotatedAt |
| AuditEvent | id, actor, action, entityId, timestampUtc, detail |

Use ADO parameterized commands for values and transactions for state transitions. Create unique indexes for `Contact.emailKey`, `Suppression.emailKey`, and `(Delivery.campaignId, Delivery.emailKey)`. Index due deliveries and attempt history. Use one local MDB and one active HTA instance; do not put the database on a network share. Restrict file permissions to the operator. Back up the MDB, INI, and template sources together while the app is closed, then test restore. The MDB, lock files, backups, and live configuration are excluded from Git.

## HTML templates with local images

The owner selects a local `.html` file. Image references must be relative to that file's folder, such as `images/postcard.jpg`. On import, resolve each path within that folder, reject traversal and absolute, remote, or data URLs, validate PNG/JPEG/GIF content and size, and snapshot bytes into `TemplateImage`. The preview uses the copied assets in a restricted context so template scripts cannot run in the privileged HTA. The mail adapter replaces each image reference with a `cid:` URL and builds an HTML `multipart/related` message with matching inline image parts. Recipient HTML must contain no `file://` or drive paths. General attachments are outside v1.

The SMTP feasibility spike must prove mandatory STARTTLS, password authentication, and inline-image MIME on the supplied server, then inspect the received message. Do not commit to CDO until its transport and MIME behavior pass this test; a small transport helper is an acceptable implementation if required.

## Configuration and credentials

The validated `config.ini` is the source of truth for system-wide settings described in `config.example.ini`. The MDB is the source of truth for campaign data and runtime state. The owner may edit INI values directly or through the HTA; the app validates changes before dispatch, records a configuration audit event, and never starts or resumes sending because of a config edit. Do not duplicate an editable setting in both places.

| INI setting group | Meaning | MDB counterpart |
| --- | --- | --- |
| `SMTP_*`, `SMTP_SECRET_ID` | Endpoint, auth identity, required TLS/STARTTLS, from and reply addresses, secret lookup | `ProtectedSecret` stores DPAPI ciphertext and rotation metadata. |
| `DB_PATH`, `DB_PROVIDER`, `DB_BITNESS` | MDB location, OLE DB provider, and launch architecture | `AppMeta` stores schema version. |
| `QUEUE_INTERVAL`, `SEND_MODE`, `DISPLAY_TIME_ZONE` | Account-wide minimum gap, allowed sending hours, display time | `DispatchState` stores pacing; `OperationalState` stores pause and review state. `SEND_MODE=always` means any hour **while the HTA is open**. |
| `TEMPLATE_ROOT`, `TEMPLATE_IMAGE_*`, `TEMPLATE_MESSAGE_MAX_BYTES` | Source folder, inline image mode, supported types, size limits | Template and image versions store approved snapshots. |
| `OPT_OUT_REVIEW_HOURS`, `BUSINESS_POSTAL_ADDRESS` | Review reminder and required footer for new campaigns | Review timestamp and campaign footer snapshot. |

`SMTP_TLS=on` means encryption is mandatory; `SMTP_STARTTLS=on` means the client must negotiate STARTTLS before authentication or message data. Port 587 is configurable. Invalid combinations, handshake failure, or invalid certificates block sending. `QUEUE_INTERVAL=300` means at least 300 seconds between attempts from this account across all campaigns. On interval change, use `max(persistedNextAllowedUtc, lastAttemptAtUtc + currentInterval)` so a new value cannot shorten the reserved gap.

The password setup/rotation action accepts the password without writing it to a command line, file, or log, protects it with Windows DPAPI `CurrentUser`, and writes only ciphertext to `ProtectedSecret`. The HTA may show whether a secret exists; plaintext should exist only for the brief SMTP authentication call. A backup restored under another Windows identity requires password re-entry. This design needs a tested implementation because script hosts do not expose DPAPI directly; a small local helper may be required.

## Open technical decisions

| Area | Decision to prove in Sprint 0 |
| --- | --- |
| SMTP | Verify actual server authentication, STARTTLS certificate validation, inline MIME, size caps, and marketing policy. |
| MDB | Choose migration and backup procedure; verify the real startup gate with the 32-bit HTA and configured file. |
| UI responsiveness | Measure synchronous send duration and choose a bounded helper if the HTA blocks too long. |
| DPAPI | Implement and test CurrentUser protection from the chosen script/helper boundary without exposing plaintext in logs or process arguments. |

## Technical source notes

- Microsoft Access connectivity and Jet architecture: https://learn.microsoft.com/en-us/sql/connect/connect-history
- Access Runtime and MDB support: https://support.microsoft.com/en-us/access/download-and-install-microsoft-365-access-runtime
- Provider bitness matching: https://learn.microsoft.com/en-us/troubleshoot/power-platform/power-automate/desktop-flows/cannot-connect-access-database-engine-ole-db
- DPAPI CurrentUser: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.dataprotectionscope
- CID MIME references: https://www.rfc-editor.org/info/rfc2387/ and https://www.rfc-editor.org/info/rfc2392/

+ 62
- 0
.specs/features/mass-mailer/spec.md Wyświetl plik

@@ -0,0 +1,62 @@
# MassMailer requirements and Scrum backlog (draft)

**Status:** Draft pending the open technical and content decisions in `../../project/PROJECT.md`.

## Functional requirements

1. Import CSV with column mapping, syntax checks, duplicate detection, source and import timestamp; show rejected rows before committing.
2. Maintain contacts and a global suppression list. Re-imports must never clear a suppression.
3. Import and manage reusable HTML templates and their local image files. Create campaigns with a selected, frozen template and image version, truthful subject, sender identity, postal address, clear ad identification, and the confirmed reply-mailbox opt-out route.
4. Preview rendered messages and send a test to an approved internal address without advancing the live queue.
5. Select eligible recipients, review counts and sample messages, approve, and schedule a campaign start instant. Dispatch occurs only while the HTA is open; overnight/weekend sends are allowed if it remains open.
6. Send one recipient at a time at the configured minimum interval; pause/resume/cancel safely. Closing the HTA stops new sends. No backlog burst after sleep, closure, or downtime; reopening requires explicit resume.
7. Record attempt, provider response, accepted/failed/uncertain state, timestamps, and campaign summary. Provider acceptance is not proof of delivery.
8. Retry only transient failures with bounded attempts and provider-directed delay. Do not automatically resend uncertain outcomes.
9. Provide a working opt-out reply address. The owner checks that mailbox daily and records requests in the suppression store. The HTA checks that store before every send. US business-to-business marketing is covered by CAN-SPAM.
10. Read all system-wide behavior from a validated INI: SMTP endpoint/auth identity/security flags; Access `.mdb` path/provider/bitness; from/reply address; minimum queue interval; send mode; display time zone; template root and image policy; opt-out review cadence; and business postal address. Store campaign-specific settings, template and image versions, contacts, suppressions, queue state, and opt-out review status in the `.mdb`. Store the SMTP password as a DPAPI-protected value in the `.mdb`, configured through a setup action. No operational value is hard-coded.
11. Define when settings take effect: transport and queue settings are revalidated before each in-app dispatch; a changed interval cannot shorten a gap already reserved; sender identity, postal address, and selected template/image version are frozen when a campaign is approved; invalid or incomplete settings block approval or sending. Configuration changes must not silently start or resume a campaign.
12. Before the HTA starts, verify the requested 32-bit/64-bit process architecture, matching Access OLE DB provider, ability to open the configured `.mdb`, and schema version. If any check fails, show a specific error and do not open the sending UI. Recheck the connection before dispatch after any database error.

## Nonfunctional requirements

- **Security:** No plaintext mail credentials or access tokens in the INI, CSV, template, log, or source code. Restrict local data to the operator account; use DPAPI CurrentUser to protect the password in the database.
- **Reliability:** Durable state transition before an attempt, single active HTA instance, recoverable restart, no catch-up burst, and auditable ambiguity.
- **HTML safety:** Treat imported contact fields, template HTML, and image paths as untrusted input. Use an allowed HTML subset and a preview that cannot execute scripts or active content inside the privileged HTA. Accept only supported image types and template-relative paths inside the selected template folder.
- **Privacy:** Minimize stored personal data; redact addresses in diagnostic logs where possible; define retention and backup policy before deployment.
- **Operability:** Visible queue state, next eligible send time, last error, pause control, exportable report, and instructions for reopening and explicitly resuming the HTA.
- **Portability:** Confirm target Windows versions, HTA/WSH policy, provider authentication, and any database/driver installation on the actual machine.
- **Deliverability:** Validate sender domain authentication and SMTP provider marketing policy and sending limits before live rollout; do not treat a five-minute gap as a guarantee of inbox placement or policy compliance. If this server is Exchange Online, Microsoft's published guidance favors a specialist provider for bulk commercial email.

## Stories and acceptance criteria

| ID | Priority | User story | Acceptance criteria / independent demo |
| --- | --- | --- | --- |
| S1 | P1 | As the owner, I can import business contacts so I can prepare a campaign. | Given a CSV with valid, invalid, and duplicate rows, preview counts and reasons; commit only valid unique contacts with source and import time. |
| S2 | P1 | As the owner, I can suppress an address so it receives no more marketing. | Given an address is suppressed before dispatch, every queued campaign skips it; importing it again does not unsuppress it. |
| S3 | P1 | As the owner, I can import, select, and preview reusable HTML templates so I know what a recipient will see. | Imported `.html` files appear in a template library; a safe preview includes required sender/opt-out details; active content and missing required fields block approval; approving a campaign freezes its selected version. |
| S4 | P1 | As the owner, I can test and approve a campaign so live sends are intentional. | Test send targets only an approved internal address; campaign starts only after review of recipient count and final template. |
| S5 | P1 | As the owner, I can schedule sends so contacts receive messages at the chosen pace. | At most one attempted recipient per interval across campaigns while the HTA is open; reopening does not cause a burst or auto-resume. |
| S6 | P1 | As the owner, I can pause or cancel a campaign so no further sends start. | After pause/cancel is committed, a new attempt does not begin; in-flight provider response is still recorded. |
| S7 | P1 | As the owner, I can see outcomes so I can fix failures. | Accepted, failed, and uncertain are distinct; report shows attempts and provider identifiers without exposing credentials. |
| S8 | P1 | As the owner, I can reconcile uncertain attempts so retries do not create avoidable duplicates. | Sender stops automatic retry on an ambiguous response; operator may mark verified sent or explicitly authorize one retry. |
| S9 | P1 | As a recipient, I can opt out so I receive no further marketing. | Every campaign names the monitored reply mailbox; the owner can record a request during the daily review; once recorded, all queued campaigns honor it before another attempt. |
| S10 | P2 | As the owner, I can receive bounce/complaint events so bad addresses are removed promptly. | A hard bounce or complaint suppresses the address before another campaign can send; duplicate events are harmless. |
| S11 | P2 | As the owner, I can personalize messages so each business sees relevant content. | Unknown merge fields and empty required values fail preflight; HTML values are escaped. |
| S12 | P2 | As the owner, I can export reports so I can review campaign results. | CSV export includes campaign, recipient, attempt time, provider result, and suppression reason. |
| S13 | P1 | As the owner, I can change system-wide settings without editing code. | The app loads the documented INI keys for SMTP, Access provider/path/bitness, pacing, open-window send mode, template/image policy, reply address, review cadence, and postal address; invalid values block startup, approval, or sending with a clear error; password rotation stores only protected bytes in the `.mdb`. |
| S14 | P1 | As the owner, I can use local images in an HTML template so my design appears in the email. | Import resolves supported template-relative images, snapshots their bytes, previews them, and sends them as inline MIME resources referenced by `cid:` URLs; changing or deleting source images after approval does not change queued mail. |
| S15 | P1 | As the owner, I get a clear database-driver check before the app opens so I can fix a 32-bit/64-bit mismatch. | A launcher tests the configured provider in the selected architecture, opens the real `.mdb`, checks schema version, and starts the HTA only on success; a missing or wrong-bitness provider produces an actionable error. |

## Sprint outline

- **Sprint 0, feasibility:** Confirm SMTP/TLS/auth and inline-image MIME compatibility, provider size limits, 32-bit HTA/Access connectivity, `.mdb` schema creation, and a safe test mailbox. Exit: one internal test message with an embedded image through the proposed adapter and a successful startup gate on the target PC.
- **Sprint 1, safe campaign creation:** S1-S4, S9's opt-out intake, S13-S15. Exit: import to reviewed test send through validated configuration and Access `.mdb` storage, with an inline image and verified suppression path.
- **Sprint 2, reliable in-app scheduling:** S5-S8. Exit: close/reopen, pacing, single-instance, crash, and pause tests pass with a fake provider.
- **Sprint 3, feedback and rollout:** S10-S12, deployment notes, backup/restore drill, limited pilot, and provider feedback validation.

Sprint lengths and estimates should be set after the provider and deployment constraints are known.

## Marketing source notes

- FTC guidance says CAN-SPAM applies to B2B commercial email, including truthful headers/subjects, identification as an ad, a physical postal address, an opt-out method, and honoring opt-outs: https://www.ftc.gov/business-guidance/resources/can-spam-act-compliance-guide-business
- Exchange Online recommends specialist providers for bulk commercial email: https://learn.microsoft.com/en-us/office365/servicedescriptions/exchange-online-service-description/exchange-online-limits

+ 49
- 0
.specs/features/mass-mailer/test-plan.md Wyświetl plik

@@ -0,0 +1,49 @@
# MassMailer test plan (draft)

## Test environments

- Run unit and scheduling tests against a fake mail adapter that records requests and injects errors; use a controllable clock.
- Use a provider sandbox or dedicated internal mailbox for integration tests. No real business contacts in automated tests.
- Perform a small live pilot only after contact eligibility, sender domain, opt-out process, and provider limits are checked.

## Test matrix

| ID | Level | Scenario | Expected result |
| --- | --- | --- | --- |
| T01 | Unit | CSV with BOM, quoted commas, blank rows, duplicate case variants, malformed addresses | Preview explains each rejection; committed contacts are unique and source is retained. |
| T02 | Unit | Import a previously suppressed address | Suppression remains active and campaign preflight excludes it. |
| T03 | Unit | Empty subject, missing sender/postal address/opt-out route, unknown merge field | Approval blocked with actionable errors. |
| T04 | Unit | Imported HTML template or contact field includes script, event handler, active object, `javascript:` URL, or HTML metacharacters | Active content is blocked; merged values are escaped; preview cannot execute script in the HTA. |
| T05 | Unit | Three queued recipients across two campaigns with five-minute minimum interval | Attempt timestamps are at least five minutes apart across both campaigns, including after a failure or retry. |
| T06 | Unit | Due send occurs overnight, on a weekend, or across a daylight-saving change while the HTA remains open | Dispatch remains eligible at any hour; UTC pacing gap holds; displayed local time is correct. |
| T07 | Integration | Attempt to launch two HTA instances | Only one instance may dispatch; one provider request is made. |
| T08 | Integration | HTA closes or crashes before, during, and after provider call | No new send begins after close; durable state survives; uncertain outcomes are flagged and not retried automatically. |
| T09 | Integration | SMTP timeout, transient 4xx, permanent 5xx, TLS failure, or authentication rejection | Transient cases back off; permanent cases stop; connection or credential issue pauses and surfaces an action. |
| T10 | Integration | Pause/cancel while a send is in flight | Current outcome is saved; no next attempt starts. |
| T11 | Integration | Sleep/offline period covering several slots | No catch-up burst on wake; next send follows the persisted pacing clock. |
| T12 | Integration | Opt-out arrives between queue selection and dispatch | Last-moment suppression check skips recipient. |
| T13 | Integration | Test-send action | Only configured internal test address receives a message; live delivery counts do not advance. |
| T14 | End to end | 10 internal test addresses across restart | Each recipient is attempted at most once in ordinary paths; report matches provider requests. |
| T15 | Operations | Backup and restore queue, campaigns, and suppressions | Restored system resumes without lost opt-outs or accidental duplicate sends. |
| T16 | Security | Inspect files/logs and run as least-privileged account | No secrets in app files or logs; unauthorized local account cannot read contact store. |
| T17 | Unit | Load example INI; change `QUEUE_INTERVAL` from 300 to another allowed value; try missing/invalid port, TLS flags, send mode, image limits, or postal address | Valid values take effect according to their documented lifecycle; invalid transport values block sending; missing postal address blocks new campaign approval with a clear error. |
| T18 | Integration | Fake SMTP server refuses STARTTLS or presents an invalid certificate | Sender fails closed; it does not send credentials or message content in plaintext. |
| T19 | Integration | Launch with x86 and x64 hosts, a missing provider, a missing MDB, and an unsupported schema version | Only a matching host/provider with an openable configured MDB and supported schema starts the HTA; other cases show actionable errors before UI launch. |
| T20 | Operations | Send an unsubscribe reply to the monitored mailbox and process it during daily review | Owner records suppression; no later queued send goes to that address. |
| T21 | Integration | Import two HTML templates, approve a campaign with one, then edit its source `.html` and image files | The campaign sends its approved HTML and image snapshots; source edits do not change queued content. |
| T22 | Integration | Send an internal test message with a local PNG/JPEG image | Raw MIME contains `multipart/related`, matching `cid:` references and inline image parts; no local paths appear in the received message. |
| T23 | Unit | Import a template with path traversal, an absolute path, unsupported image type, or oversized image | Import blocks the asset with a clear reason and never reads outside the selected template folder. |
| T24 | Manual integration | Open the internal test message in the intended desktop and web mail clients | HTML and inline images render as expected; reply address, postal address, and opt-out wording are visible. |
| T25 | Integration | Change sender/reply/postal INI values and source template after approving a campaign | Existing campaign keeps its approved identity, footer, HTML, and images; a new campaign can use the new values. |
| T26 | Unit | Shorten or lengthen `QUEUE_INTERVAL` while a prior attempt's gap is still pending | The next send occurs no earlier than both the old reserved time and the new interval measured from the last attempt. |
| T27 | Operations | Restore the database under another Windows identity | Campaign and suppression data restore; SMTP password cannot be decrypted and must be entered again before sending. |
| T28 | Integration | Close the HTA with a due campaign, wait past several intervals, then reopen | No sends occur while closed; reopening requires explicit resume and causes no catch-up burst. |
| T29 | Integration | Invoke provider probe from both 32-bit and 64-bit script hosts on target PC | Diagnostic reports actual MDB create/write/read capability per provider and architecture; startup uses the verified architecture. |

## Release gates

1. All P1 stories and T01-T29 applicable to the chosen provider pass.
2. Sender domain and provider limits verified; the system can display provider rejection and halt safely.
3. Unsubscribe route is tested end to end; a recorded opt-out takes effect before the next dispatch. Any manual mailbox procedure has an owner and a tested processing schedule.
4. Operator receives deployment, backup/restore, pause, reconciliation, and incident instructions.
5. Pilot results reviewed for acceptance, bounces, complaints, and reporting accuracy before expanding recipients.

+ 57
- 0
.specs/project/PROJECT.md Wyświetl plik

@@ -0,0 +1,57 @@
# MassMailer: draft project brief

## Vision

A Windows desktop tool for the owner's 9 x 12 postcard business to prepare and schedule individual marketing emails to business contacts. The owner can see exactly who is queued, stop a campaign, and review what the mail provider accepted.

## Known requirements

- UI technology requested: HTA with VBScript/JScript.
- Message type: marketing for the postcard business.
- Audience: US businesses, using publicly listed email addresses. Store the source URL/reference and collection date for each contact; public listing alone is not an opt-in record.
- Transport: configurable SMTP. Initial settings are `netsol-smtp-oxcs.hostingplatform.com:587`, user/from `danielcovington@postcardmojo.com`, TLS required, STARTTLS required. Authentication uses a username and password. The password must not be shared in project files or chat.
- Pacing: configurable `QUEUE_INTERVAL=300` seconds as the initial minimum gap between individual send attempts (nominally 12/hour or 288/day if sending continuously).
- Sending occurs only while the owner is using the HTA. Closing the app stops new sends and leaves the campaign paused until the owner explicitly resumes it.
- Expected list size: fewer than 500 business addresses.
- Sending window: no hour or weekday restriction while the HTA is open. Timestamps are stored in UTC and displayed in local time.
- Message authoring: reusable HTML templates with embedded local image files for v1. A campaign uses a frozen template and image snapshot once approved; personalization is a later enhancement.
- Opt-outs: `danielcovington@postcardmojo.com` is the monitored reply mailbox. The owner checks it daily and enters requests into the suppression list. The HTA checks that list before later sends.
- The owner confirms the SMTP provider permits this marketing use; connection and STARTTLS compatibility still need a technical check.
- Backend: an Access `.mdb` database via 32-bit `Microsoft.ACE.OLEDB.12.0` on the current PC. The UI launcher must check process bitness and actually open the selected provider before starting the HTA. Current diagnostics passed `.mdb` create/write/read in 32-bit hosts and failed in 64-bit hosts.
- Configuration rule: system-wide settings are editable in a validated `.ini` file; campaign choices, approved template/image versions, contacts, suppressions, queue state, review status, and a protected SMTP password value are editable or managed through the `.mdb`. No operational value is compiled into the scripts.
- Deliverables in this planning phase: requirements, architecture, Scrum stories, and test plan. No sending code or live email is in scope yet.

## Working assumptions to validate

1. One email means one message addressed to one recipient; no CC/BCC batching.
2. The configured gap is a **minimum interval between all send attempts from the sending account**, across campaigns and including retries. The HTA starts at most one send at a time. The interval should have a safe minimum that the provider permits.
3. There is no daily or weekly blackout window in v1. The HTA must be open to send. Store instants in UTC, show local time in the UI, and do not burst through missed slots after an outage or app closure.
4. Campaigns require a preflight review and explicit start action. Imports never start sending by themselves.
5. Every contact has a source record and can be suppressed. Once an opt-out is recorded, future marketing to that address must stop before the next dispatch.
6. The first deployment is a single Windows machine, one operator, and one HTA process. Multiuser and cloud hosting are later decisions.
7. The example INI leaves `BUSINESS_POSTAL_ADDRESS` blank; campaign approval must require the real business address.

## Success measures for v1

- No recipient is sent after they are suppressed, and no address receives two attempts for the same campaign through ordinary restart/retry paths.
- Configured pacing is respected across normal sends and retries.
- A crash or reboot preserves campaign and delivery state, with uncertain outcomes visible for manual reconciliation.
- Closing the HTA starts no new sends; reopening it does not resume a campaign without an explicit owner action.
- The operator can import, review, test-send, schedule, pause, resume, and export an audit report without editing scripts.

At the initial five-minute gap, a campaign with 500 recipients needs at least 41 hours 35 minutes from first to last send if it runs continuously, before retries or provider delays. At the stated size of fewer than 500 contacts, a full-list run is slightly shorter. The configurable gap determines campaign lead time.

## Open decisions

| ID | Decision needed | Why it matters |
| --- | --- | --- |
| D2 | Verify that the supplied SMTP server supports mandatory STARTTLS on port 587 with username/password authentication | Determines whether a VBScript/JScript-compatible transport can safely connect. |
| D3 | Confirm the installed `.mdb` path, selected provider, and 32-bit HTA launch path during deployment | The startup gate must open the real database from the same architecture as the app. |
| D4 | Confirm actual campaign frequency during pilot | Helps tune reporting and backups; the design sizes for at most 500 recipients per campaign. |

Resolved decisions: D1 is US-only publicly listed business addresses; D5 is a configurable account-wide minimum gap while the app is open, with no catch-up bursts; D6 is reusable HTML templates with embedded local images; D7 is `danielcovington@postcardmojo.com` as the owner-monitored reply mailbox checked daily. The owner confirms SMTP marketing use is permitted and that sending stops when the HTA closes.

## Source notes

- US commercial email rules also cover business-to-business mail: https://www.ftc.gov/business-guidance/resources/can-spam-act-compliance-guide-business
- UK business email rules vary by the type of business contact: https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/business-to-business-marketing/

+ 12
- 0
README.md Wyświetl plik

@@ -0,0 +1,12 @@
# MassMailer planning workspace

Draft plan for a Windows HTA tool that schedules individual marketing emails for a 9 x 12 postcard business.

- [Project brief and open decisions](.specs/project/PROJECT.md)
- [Requirements, Scrum stories, and sprint outline](.specs/features/mass-mailer/spec.md)
- [Architecture and data model](.specs/features/mass-mailer/design.md)
- [Test plan and release gates](.specs/features/mass-mailer/test-plan.md)
- [Example SMTP configuration without a password](config.example.ini)
- [Access provider probe for 32-bit and 64-bit hosts](tools/probe-mdb-providers.vbs)

These are planning documents plus a working Access provider diagnostic. The app will send only while its HTA window is open. System-wide settings belong in the INI; campaign settings, templates, contacts, queue state, and protected secrets belong in an Access `.mdb`. SMTP transport compatibility still needs an internal test message.

+ 34
- 0
config.example.ini Wyświetl plik

@@ -0,0 +1,34 @@
# Example system-wide settings. Copy to a local config file when implementing.
# The SMTP password is configured through a setup action and stored as a
# DPAPI-protected value in the database. Never add SMTP_PASSWORD here.
SMTP_HOST=netsol-smtp-oxcs.hostingplatform.com
SMTP_PORT=587
SMTP_USER=danielcovington@postcardmojo.com
SMTP_TLS=on
SMTP_STARTTLS=on
SMTP_FROM=danielcovington@postcardmojo.com
SMTP_REPLY_TO=danielcovington@postcardmojo.com
SMTP_SECRET_ID=primary_smtp
DB_PATH=data/MassMailer.mdb
DB_PROVIDER=Microsoft.ACE.OLEDB.12.0
DB_BITNESS=x86
QUEUE_INTERVAL=300
SEND_MODE=always
DISPLAY_TIME_ZONE=local
TEMPLATE_ROOT=templates
TEMPLATE_IMAGE_MODE=inline_cid
TEMPLATE_IMAGE_TYPES=png,jpeg,gif
TEMPLATE_IMAGE_MAX_BYTES=2097152
TEMPLATE_MESSAGE_MAX_BYTES=10485760
OPT_OUT_REVIEW_HOURS=24
BUSINESS_POSTAL_ADDRESS=

# QUEUE_INTERVAL is the minimum number of seconds between attempts across
# all campaigns using this SMTP account. TLS=on plus STARTTLS=on means the
# sender must upgrade to TLS and fail closed if the server cannot do so.
# SMTP_REPLY_TO is the monitored mailbox confirmed by the owner.
# DB_PROVIDER and DB_BITNESS are checked before the HTA opens. On this PC,
# the listed ACE provider passed a 32-bit .mdb create/write/read probe.
# The image and message caps are initial application limits to validate
# against the SMTP provider. BUSINESS_POSTAL_ADDRESS must be filled before
# a marketing campaign can be approved.

+ 71
- 0
tools/probe-mdb-providers.vbs Wyświetl plik

@@ -0,0 +1,71 @@
' Run with 32-bit and 64-bit cscript.exe to verify actual .mdb capability.
' Creates and removes a small temporary .mdb; it never touches campaign data.
Option Explicit

Dim fso, hostPath, providers, provider
Set fso = CreateObject("Scripting.FileSystemObject")
hostPath = WScript.FullName
providers = Array("Microsoft.Jet.OLEDB.4.0", "Microsoft.ACE.OLEDB.12.0", "Microsoft.ACE.OLEDB.16.0")

WScript.Echo "Script host: " & hostPath
For Each provider In providers
ProbeProvider CStr(provider)
Next

Sub ProbeProvider(providerName)
Dim testPath, catalog, connection, recordset, errorText, okay
testPath = fso.BuildPath(fso.GetSpecialFolder(2), _
"MassMailer_" & fso.GetBaseName(fso.GetTempName()) & ".mdb")
okay = False
errorText = ""

On Error Resume Next
Set catalog = CreateObject("ADOX.Catalog")
If Err.Number <> 0 Then
errorText = Err.Description
Err.Clear
Else
catalog.Create "Provider=" & providerName & ";Data Source=" & testPath & _
";Jet OLEDB:Engine Type=5;"
If Err.Number <> 0 Then
errorText = Err.Description
Err.Clear
Else
Set catalog = Nothing
Set connection = CreateObject("ADODB.Connection")
connection.Open "Provider=" & providerName & ";Data Source=" & testPath & ";"
If Err.Number <> 0 Then
errorText = Err.Description
Err.Clear
Else
connection.Execute "CREATE TABLE Probe (Id INTEGER)"
connection.Execute "INSERT INTO Probe (Id) VALUES (1)"
Set recordset = connection.Execute("SELECT Id FROM Probe")
If Err.Number <> 0 Then
errorText = Err.Description
Err.Clear
ElseIf recordset.EOF Then
errorText = "Readback returned no rows"
ElseIf CLng(recordset.Fields("Id").Value) <> 1 Then
errorText = "Readback returned wrong value"
Else
okay = True
End If
End If
End If
End If

If Not recordset Is Nothing Then recordset.Close
If Not connection Is Nothing Then connection.Close
Set recordset = Nothing
Set connection = Nothing
Set catalog = Nothing
If fso.FileExists(testPath) Then fso.DeleteFile testPath, True
On Error GoTo 0

If okay Then
WScript.Echo providerName & ": OK (create/write/read .mdb)"
Else
WScript.Echo providerName & ": UNAVAILABLE (" & errorText & ")"
End If
End Sub

Ładowanie…
Anuluj
Zapisz

Powered by TurnKey Linux.