πŸ“‹ Migration Summary

Source Tables: 7
Target Models: 6
Decisions: 8

Migrating notification system, message queues, letters, and webhook configurations for automated communications.

🎯 Key Actions

⚠️ Key Challenges

Area 8: Notifications & Letters

Overview

Migration of notifications and letters from DTX 1.0 to DTX 2.0. This is Phase 4 β€” depends on Areas 2, 3 (Customers/Accounts, Vendors/Records).

DTX 1.0's notification system was built around a settings + template + sent-history architecture: notification stored per-customer timing schedules, notification_webhook mapped events to email recipient lists (PHP serialized), letter stored email templates with __PLACEHOLDER__ macros, messagequeue stored sent emails with full HTML bodies, and envelope tracked broker responses.

DTX 2.0 has a fully rebuilt notification system in the docutrax_utils app with a queue-based architecture: NotificationSchedule (account-scoped, per-type/per-sequence rows with cumulative timing), NotificationQueue (pending items processed by Celery tasks), EmailLog (delivery tracking), and EmailTemplate (reusable templates with {PLACEHOLDER} variables). Additionally, InAppNotification provides bell-icon notifications β€” a feature with no DTX 1.0 equivalent.

All target models exist. No missing models (unlike Area 9).

Source Tables (7)

DTX 1.0 Table Rows (sample) Type DTX 2.0 Target
notification 100 Configuration NotificationSchedule (docutrax_utils)
notification_webhook 100 Configuration NotificationSchedule.email_recipients
notification_lookup 100 Denormalized join β€” (replaced by Account FK)
notification_audit 100 Audit trail β€” (historical)
letter 100 Templates EmailTemplate (docutrax_utils)
messagequeue 100 Sent history EmailLog (docutrax_utils)
envelope 100 Response tracking No target model

Target Models

DTX 2.0 Model App File Lines
NotificationSchedule docutrax_utils docutrax_utils/models.py 307–455
EmailLog docutrax_utils docutrax_utils/models.py 457–597
NotificationQueue docutrax_utils docutrax_utils/models.py 641–743
EmailTemplate docutrax_utils docutrax_utils/models.py 745–837
InAppNotification docutrax_utils docutrax_utils/models.py 1170–1369
CronSetting docutrax_utils docutrax_utils/models.py 599–639

Supporting Infrastructure

Component File Purpose
EmailNotificationService docutrax_utils/services/email_notification_service.py Template rendering, recipient resolution, send logic
NotificationQueueService docutrax_utils/services/notification_queue_service.py Queue building, timing calculations, guard checks
email_service.py docutrax_utils/services/email_service.py Low-level email send with Celery fallback
build_notification_queue docutrax_utils/tasks.py Celery task: hourly queue building
process_notification_queue docutrax_utils/tasks.py Celery task: daily send at 8 AM
ConditionalEmailBackend docutrax_utils/email_backends.py Safety filter for test/staging
Email templates docutrax_utils/templates/emails/ HTML templates (base, compliance, expiration, etc.)

Dependencies

Depends On What's Needed
Area 2: Customers/Accounts notification.customer_id β†’ Account mapping; AccountEmail for recipients
Area 3: Vendors/Records messagequeue.vendor_id β†’ Record mapping; recipient addresses
Area 4: Certificates/Documents Expiration events trigger notifications
Depended On By What It Provides
Area 10: Reporting Notification metrics for reports

Decisions

Decision #1: notification + notification_webhook β†’ NotificationSchedule

This is the most complex migration in this area. DTX 1.0 stores notification settings as wide rows (one row per customer/type with ~20 timing columns). DTX 2.0 uses narrow rows (one row per account/type/sequence with a single days_after column).

DTX 1.0 structure: Each customer has 2 notification rows (type=COI and type=DOCUMENT). Each row contains columns for multiple reminder stages: - CR* = Certificate Request settings (first remind, second remind, non-response alert, restart) - CE* = Certificate Expiration settings (start-to-expire, first remind, expired, second remind, alert, restart) with Before/After timing - CNC* = Non-Compliance settings (first, second, alert, restart) - DR* = Document Request settings (first remind, second remind, non-response alert)

DTX 2.0 structure: Each account has multiple NotificationSchedule rows, one per (type, sequence) combination: - type: coi_request, coi_non_compliance, coi_expiration, document_request, document_non_compliance, document_expiration, coi_review, document_review - sequence: initial, reminder, final, alert, restart_notices, soon_to_expire, expired - days_after: cumulative days from T0 - email_recipients: JSON array (populated from notification_webhook)

Column mapping β€” COI notification row:

DTX 1.0 Column DTX 2.0 type DTX 2.0 sequence DTX 2.0 days_after
(implicit) coi_request initial 0
CRFRRSetting coi_request reminder CRFRRSetting (cumulative)
CRSRRSetting coi_request final CRFRR + CRSRR (cumulative)
CRNRAlertSetting coi_request alert CRFRR + CRSRR + CRNRAlert (cumulative)
CRRestartSetting coi_request restart_notices CRFRR + CRSRR + CRNRAlert + CRRestart (cumulative)
CESTESetting / CEFERTiming=Before coi_expiration soon_to_expire CESTESetting, timing_type=before
CEFERSetting / CEFERTiming coi_expiration reminder CEFERSetting, timing_type per CEFERTiming
CEEXPSetting / CEEXPTiming coi_expiration expired CEEXPSetting, timing_type per CEEXPTiming
CESERSetting / CESERTiming coi_expiration final CESERSetting, timing_type per CESERTiming
CEEXPAlertSetting / CEEXPAlertTiming coi_expiration alert CEEXPAlertSetting, timing_type per CEEXPAlertTiming
CERestartSetting coi_expiration restart_notices CERestartSetting, timing_type=after
CNCFSetting coi_non_compliance reminder CNCFSetting (cumulative)
CNCSSetting coi_non_compliance final CNCF + CNCS (cumulative)
CNCAlertSetting coi_non_compliance alert CNCF + CNCS + CNCAlert (cumulative)
CNCRestartSetting coi_non_compliance restart_notices CNCF + CNCS + CNCAlert + CNCRestart (cumulative)

Column mapping β€” DOCUMENT notification row:

DTX 1.0 Column DTX 2.0 type DTX 2.0 sequence DTX 2.0 days_after
(implicit) document_request initial 0
DRFRRSetting document_request reminder DRFRRSetting (cumulative)
DRSRRSetting document_request final DRFRR + DRSRR (cumulative)
DRNRAlertSetting document_request alert DRFRR + DRSRR + DRNRAlert (cumulative)

Email recipients from notification_webhook:

For each (customer_id, type, selector) in notification_webhook: 1. Deserialize PHP serialized webhook field β†’ extract email array 2. Map selector to (type, sequence):

DTX 1.0 selector DTX 2.0 type DTX 2.0 sequence
NONCOMP coi_non_compliance / document_non_compliance initial
NONCOMP_REMIND_1 *_non_compliance reminder
NONCOMP_REMIND_2 *_non_compliance final
EXPIRE_SOON *_expiration soon_to_expire
EXPIRE_REMIND_1 *_expiration reminder
EXPIRE_REMIND_2 *_expiration final
EXPIRED *_expiration expired
REQUEST_REMIND_2 *_request final
  1. Store as JSON array in NotificationSchedule.email_recipients

Yield: Each DTX 1.0 customer produces up to ~30 NotificationSchedule rows (COI: 5 request + 6 expiration + 5 non-compliance = 16; DOCUMENT: 4 request + similar = ~14).

Additional fields: - notification.send_hold β†’ NotificationSchedule.is_active (SEND=True, HOLD=False) - notification.user_id β†’ store in ownership fields if needed - notification.create_time β†’ use as creation timestamp - notification.customer_id β†’ remap to NotificationSchedule.account FK via Area 2 mapping


Decision #2: notification_lookup β†’ Drop

The notification_lookup table (12 columns) is a denormalized join table mapping customers to their notification configurations via coi_notification_id and doc_notification_id FKs, plus a broker_id reference.

Drop this table. DTX 2.0 links NotificationSchedule directly to Account via FK. The customer→notification relationship is implicit. The broker_id reference is irrelevant (Area 9 found no Broker model in DTX 2.0).

Data preserved: The coi_send_hold / doc_send_hold overrides and coi_dow_bitmap / doc_dow_bitmap day-of-week settings from this table are partially captured by Decision #1 (send_hold β†’ is_active). The dow_bitmap values have no DTX 2.0 equivalent (see Decision #7).


Decision #3: notification_audit β†’ Don't migrate

The notification_audit table (25 columns) records BEFORE/AFTER snapshots of notification setting changes. It has 3 fewer columns than the live notification table (missing CRRestartSetting, CERestartSetting, CNCRestartSetting), indicating schema drift β€” columns were added to notification without updating the audit table.

Don't migrate. This is historical audit data for a settings table that is being completely restructured (Decision #1). The old BEFORE/AFTER snapshots of DTX 1.0-format settings have no value in DTX 2.0's new architecture. DTX 2.0 tracks notification activity via EmailLog and NotificationQueue rather than settings-change auditing.

Keep CSV as archive for reference.


Decision #4: letter β†’ EmailTemplate

The letter table (9 columns) stores customer-specific email templates with HTML bodies containing __PLACEHOLDER__ macros (e.g., __DATE__, __SO_NAME__, __PH_NAME__, __EXPIRING_POLICIES__).

Column mapping:

DTX 1.0 DTX 2.0 Action
customer_id entity_type + entity_id Set entity_type='account', entity_id = remapped Account ID
type (part of name) Incorporate into template name (e.g., "COI Expire Remind 2")
selector (part of name) Incorporate into template name
subject subject Direct, with placeholder remapping
letter_body body_html Direct, with placeholder remapping
edit_time modified Use as modification timestamp
name β€” Drop (always NULL in sample)
addl_email β€” Drop (always NULL)
internal_reference_name β€” Drop (always NULL)
β€” body_text New β€” leave NULL (plain text fallback)
β€” from_email New β€” leave NULL (use system default)
β€” from_name New β€” leave NULL (use system default)
β€” variables New β€” populate from detected placeholders in body_html
β€” is_active New β€” set True
β€” is_default New β€” set False
β€” language New β€” set 'en' (default); set 'es' for Spanish templates

Placeholder macro remapping:

DTX 1.0 DTX 2.0 Notes
__DATE__ {DATE} Current date
__SO_NAME__ {SO_NAME} Account/service owner name
__SO_LOGO__ {SO_LOGO} Account logo URL
__SO_ADDRESS1__ {SO_ADDRESS1} Account address
__SO_CITY__, __SO_STATE__, __SO_ZIPCODE__ {SO_CITY}, {SO_STATE}, {SO_ZIPCODE} Address components
__PH_NAME__ {PH_NAME} Policyholder/record name
__PH_ADDRESS1__, etc. {PH_ADDRESS1}, etc. Record address components
__PH_CONTACT_EMAIL__ {PH_CONTACT_EMAIL} Record email
__CH_NAME__ {CH_NAME} Certificate holder name
__SUBJECT__ {SUBJECT} Dynamic subject line
__EXPIRING_POLICIES__ {EXPIRING_POLICIES} Expiring policy list HTML
__DOCUMENTS_EXPIRING__ {DOCUMENTS_EXPIRING} Expiring documents list HTML
__REQUIREMENTS__ {REQUIREMENTS} Requirements list HTML
__DOCUMENT_REQUIREMENTS__ {DOCUMENT_REQUIREMENTS} Document requirements list
__CUSTOM_MESSAGE_1__ {CUSTOM_MESSAGE_1} Custom per-customer message
__SITE_URL__ {SITE_URL} Upload portal URL
__DOCUMENTS_URL__ {DOCUMENTS_URL} Document upload URL

Approach: - Regex-replace all __([A-Z0-9_]+)__ patterns with {$1} in both subject and body_html. - Detect unique placeholders from body_html and populate the variables JSON list. - Set name = "{type} {selector}" (e.g., "COI EXPIRE_REMIND_2"). - Some templates contain Spanish text β€” set language='es' for those, or create bilingual variants.

Pre-migration action: Build a complete list of all __PLACEHOLDER__ macros used across all letter rows and verify each maps to a DTX 2.0 template variable supported by EmailNotificationService.get_record_context().


Decision #5: messagequeue β†’ Don't migrate (archive)

The messagequeue table (15 columns) stores complete sent email records including full HTML bodies (letter_body), recipient addresses, send/read timestamps, and GUID-based tracking pixels.

Don't migrate. Rationale: - Structural mismatch: DTX 2.0's EmailLog tracks delivery metadata (schedule FK, delivery_status, context_data) β€” it does not store full HTML email bodies. The monolithic letter_body field (averaging ~57 lines of HTML per row) has no target column. - Historical data: These are records of emails already sent. They have no operational value in the new system. - Tracking infrastructure change: DTX 1.0 used GUID-based tracking pixels (risktoolbox.com/letters/messageread/{GUID}) and read_time tracking. DTX 2.0 uses delivery_status from the email provider (Mailgun). - Volume concern: The full messagequeue table likely contains thousands of rows, each with multi-KB HTML bodies.

Keep CSV as archive. If historical email audit is needed, it can be queried from the archived data.


Decision #6: envelope β†’ No target model

The envelope table (9 columns) tracks broker/vendor responses to notification emails. When a vendor receives a compliance request email with a portal link, the envelope records their interaction: whether they accessed it, how many clicks, and the outcome (Completed, Declined, Voided). The vendor_data column contains PHP serialized contact information (doubly-serialized β€” the email field within is itself a PHP serialized array).

DTX 2.0 does not have an Envelope model. The DTX 2.0 system uses magic links (cryptographically signed URLs) for vendor document upload, tracked through the standard Document upload flow rather than a dedicated response-tracking table.

The team must decide: 1. Archive and discard β€” The envelope tracking was tightly coupled to the old notification system. DTX 2.0's magic link + Document upload flow replaces this functionality differently. 2. Create a ResponseTracking model β€” If historical response rates (Completed vs Declined vs Voided) are valuable for analytics or to benchmark the new system's vendor engagement rates. 3. Extract aggregate metrics only β€” Summarize response rates per customer/vendor and store as reference data without migrating individual envelope records.

Notable data in envelope: - event distribution: Voided (60%), Completed (36%), Declined (3%), NULL (1%) β€” shows most compliance requests expire without vendor action. - vendor_data: Contains vendor contact snapshots (name, address, email, phone). These are point-in-time copies, not canonical records.


Decision #7: send_hold and dow_bitmap capability gap

DTX 1.0 has two notification control settings that map imperfectly to DTX 2.0:

1. send_hold (SEND/HOLD): - notification.send_hold: Global toggle per customer/type - notification_lookup.coi_send_hold / doc_send_hold: Per-customer overrides - DTX 2.0 mapping: NotificationSchedule.is_active (True/False). Partial mapping β€” DTX 2.0 is per-schedule-row, not per-customer-type. A customer with send_hold=HOLD for COI would set is_active=False on all their COI-type schedules.

2. dow_bitmap (day-of-week): - notification.dow_bitmap: Bitmask for which days notifications can be sent (e.g., 32 = Friday only, 62 = Mon-Fri, 127 = every day) - DTX 2.0 has no equivalent. The Celery task process_notification_queue runs daily at 8 AM regardless of day-of-week. - Impact: Customers who restricted notifications to specific days will now receive them any day. - Options: - A. Accept the gap β€” process notifications daily (simplification) - B. Add a dow_bitmap or send_days field to NotificationSchedule to preserve per-customer day restrictions - C. Store in metadata/notes for manual configuration later


Decision #8: Deferred references from other areas

Source Area Deferred Item Resolution
Area 1: Users & Auth alert_subscribers table (user_id + customer_id alert preferences, coi_dow_bitmap) Resolved β€” dropped per Area 1 Decision #5. DTX 2.0 NotificationSchedule replaces user-level alert toggles with account-level schedules. The coi_dow_bitmap gap is covered in Decision #7.
Area 1: Users & Auth bounced_email table Resolved β€” not migrated per Area 1 Decision #11. DTX 2.0 tracks bounces via Mailgun delivery webhooks and EmailLog.delivery_status.
Area 3: Vendors/Records Vendor ping fields (ping_date, ping_to, ping_cc, ping_subject, ping_message, ping_note) Resolved β€” all dropped per Area 3. Replaced by NotificationQueue + EmailLog tracking.
Area 3: Vendors/Records additional_language, custom_message_2 Resolved β€” stored as reference data per Area 3. Can be used to populate {CUSTOM_MESSAGE_1} in EmailTemplate if needed.
Area 9: Brokers/Programs program.envelope_id FK Resolved β€” depends on Decision #6. Program table not migrated (Area 9). If envelope is not migrated, this FK is moot.

Known Issues

# Issue Impact Mitigation
1 PHP serialized email arrays in notification_webhook.webhook and envelope.vendor_data Requires deserialization to extract email addresses. envelope.vendor_data is doubly-serialized (email field is itself a serialized array). Use a PHP unserialize library (Python: phpserialize package) during migration ETL.
2 Cumulative vs incremental timing ambiguity in DTX 1.0 CR/CE/CNC/DR settings may be incremental intervals or cumulative offsets from T0. The sample shows all-identical values (14/14/5/30), making it impossible to distinguish from sample data alone. Pre-migration action: examine DTX 1.0 PHP code to confirm whether settings are incremental or cumulative.
3 dow_bitmap encoding Bitmask values (0, 2, 32, 36, 42, 64, 127) need interpretation. Likely Mon=1, Tue=2, Wed=4, Thu=8, Fri=16, Sat=32, Sun=64, but the exact bit assignment is unconfirmed. Pre-migration action: verify bit-to-day mapping from DTX 1.0 source code.
4 Schema drift between notification (28 cols) and notification_audit (25 cols) 3 "restart" columns missing from audit table β€” added after the audit table was created. No impact (audit not migrated β€” Decision #3).
5 letter table has no surrogate PK Composite key (customer_id, type, selector). DTX 2.0 EmailTemplate uses auto-increment PK. Generate new PKs during migration. Use unique constraint (entity_type, entity_id, name, language) for deduplication.
6 __DATE__ placeholder un-replaced in some messagequeue bodies Indicates the template engine sometimes stored pre-rendered HTML with placeholder artifacts. Only relevant if messagequeue is migrated (Decision #5 says don't).
7 Homogeneous sample data notification sample: all send_hold=HOLD, all dow_bitmap=32. messagequeue: single customer, single event type. letter: single selector. Full dataset likely has more variety. Pre-migration: query distinct values from full DTX 1.0 database.
8 Bilingual templates Some letter templates contain Spanish text ("Favor de enviarle esta solicitud..."). Set language='es' for Spanish templates, or create separate en/es EmailTemplate rows. Detect via regex on letter_body.
9 Envelope doubly-serialized vendor_data Email field within the serialized array is itself a PHP serialized array. Requires recursive deserialization. Only relevant if envelope data is preserved (Decision #6).

Pre-Migration Actions

# Action Blocking?
1 Confirm timing semantics: Examine DTX 1.0 PHP source code to determine whether CR/CE/CNC/DR setting values are incremental intervals or cumulative offsets from T0. Yes β€” needed for correct days_after calculation in Decision #1.
2 Verify dow_bitmap encoding: Confirm which bit corresponds to which day of the week (Mon=1 or Sun=1?). Yes β€” needed if team chooses to preserve dow_bitmap per Decision #7 Option B.
3 Inventory all PLACEHOLDER macros: Query full letter table for all distinct placeholder patterns used. Verify each maps to a DTX 2.0 template variable in EmailNotificationService.get_record_context(). Yes β€” needed for Decision #4 placeholder remapping.
4 Query full notification variety: Get distinct send_hold, dow_bitmap, and type distributions from full DTX 1.0 database to understand actual configuration diversity. No β€” but helps gauge migration scope.
5 Assess notification_webhook selector coverage: Get full list of distinct selectors (beyond the 8 in sample) to confirm the selector β†’ (type, sequence) mapping is complete. Yes β€” needed for Decision #1 email recipient mapping.

Migration Order

Step 1: Migrate letter β†’ EmailTemplate (templates needed before schedules reference them)
Step 2: Migrate notification + notification_webhook β†’ NotificationSchedule (config with email recipients)
Step 3: Drop notification_lookup (no migration needed)
Step 4: Drop notification_audit (no migration needed)
Step 5: Archive messagequeue (no migration, keep CSV)
Step 6: Resolve envelope per team decision
Step 7: Bootstrap NotificationQueue and EmailLog (empty β€” populated by DTX 2.0 operations going forward)

Cross-Area References

Area Reference Status
Area 1: Users & Auth alert_subscribers dropped; bounced_email not migrated Resolved
Area 2: Customers/Accounts notification.customer_id β†’ Account remap; AccountEmail for recipients Resolved β€” use Area 2 mapping
Area 3: Vendors/Records Vendor ping fields dropped; additional_language, custom_message_2 stored as ref Resolved
Area 9: Brokers/Programs notification_lookup.broker_id (dropped with table); program.envelope_id Resolved β€” both tables dropped/dependent on Decision #6
Area 11: File Repository No direct references N/A

Decision Summary

# Decision Status Approach
1 notification + notification_webhook β†’ NotificationSchedule Documented See decision details below
2 notification_lookup β†’ Drop Documented See decision details below
3 notification_audit β†’ Don't migrate Documented See decision details below
4 letter β†’ EmailTemplate Documented See decision details below
5 messagequeue β†’ Don't migrate (archive) Documented See decision details below
6 envelope β†’ No target model Documented See decision details below
7 send_hold and dow_bitmap capability gap Documented See decision details below
8 Deferred references from other areas Documented See decision details below