application capa

application-messaging

Forge messaging module — send_jobs + per-provider credentials + sender + prompt_builder. Sprint 59.

Forge messaging module — send_jobs + per-provider credentials + sender + prompt_builder. Sprint 59.

Tierapplication
Roleunclassified (baselined)
Pathcrates/application/messaging
Edition2024
Targetsapplication_messaging, pg_forge_messaging
Public items46 across 9 modules
Tests8

What it is for

application-messaging — Layer 3 module. Sprint 59: services + workers extracted.

B-009: before this, migrations() returned Vec::new() and migrate_pending returned Ok(0) while ignoring the pool, so conversations/conversation_participants/chat_messages/send_jobs/ messaging_credentials (crates/infrastructure/communication/migrations/, files 000004) were never created by anything that actually called this module's own migration path. application-messaging is the one forge-* module that consumes this schema directly (send_jobs via services::messaging, messaging_credentials via services::messaging_credentials, ChatMessage/conversation types via workers::sender's re-export) — application-communication re-exports the same infrastructure_communication crate but owns a distinct, unrelated communications timeline table of its own (crates/application/communication/migrations/0001_communication_init.sql), not this schema, so this wiring does not duplicate that crate's. infrastructure-ai's own 001_ai_credentials.sql (also a dependency of this crate, per Cargo.toml) is deliberately NOT vendored here: application-ai already owns and wires it (crates/application/ai/src/lib.rs), and vendoring the same file into a second module's ledger would double-apply it.

000_create_communication_tables.sql FKs conversation_participants and chat_messages to parties(id), and its triggers call update_updated_at_column() (crates/foundation/basemodels/migrations/001_base_model_infrastructure.sql, applied by the central platform set) — a host composing this module must apply those first, same ordering caveat application-calendar's own test documents. 001/002 are legacy person_id->party_id cleanup migrations that no-op gracefully on a fresh install (their own file headers say so); 003/004 (send_jobs, messaging_credentials) carry no FKs.

No permission_codes() added: neither infrastructure-communication (crates/infrastructure/communication/src/) nor infrastructure-ai defines permission/capability constants for this crate's own surface to draw from — nothing to wire without inventing names.

Capabilities

MessagingModule

application-messaging — Layer 3 module. Sprint 59: services + workers extracted.

Item
pub struct MessagingModule
MessagingModule :: fn new() -> Self
MessagingModule :: fn name(& self) -> & 'static str
MessagingModule :: fn version(& self) -> & 'static str
MessagingModule :: fn migrations(& self) -> Vec <MigrationSet>
MessagingModule :: async fn migrate_pending(& self, pool : & PgPool) -> Result <u32, MigrationError>

api (other)

application-messaging HTTP API. Sprint 65: extracted from rust-cms-engine.

Item
fn admin_router <S>() -> Router <S> where S : Clone + Send + Sync + 'static, PgPool : FromRef <S>,

api::messaging_admin (other)

Messaging admin REST endpoints (Sprint 44 — Gate 1.5 Q3).

Item
fn router <S>() -> Router <S> where S : Clone + Send + Sync + 'static, PgPool : FromRef <S>,

api::messaging_settings_admin (other)

Messaging settings admin REST endpoints (Sprint 50 → 53).

Item
fn router <S>() -> Router <S> where S : Clone + Send + Sync + 'static, PgPool : FromRef <S>,

services::messaging (other)

Messaging service (Sprint 44).

Item
fn default_next_attempt_at(attempt_count : i32) -> DateTime <Utc>
async fn enqueue_send(pool : & PgPool, req : EnqueueRequest) -> Result <SendJob, SendJobError>
async fn get_send_job(pool : & PgPool, id : Uuid) -> Result <SendJob, SendJobError>
async fn claim_pending(pool : & PgPool, channel : SendChannel, limit : i64,) -> Result <Vec <SendJob>, SendJobError>
async fn mark_sent(pool : & PgPool, id : Uuid, provider_message_id : Option <& str>,) -> Result <SendJob, SendJobError>
async fn mark_failed(pool : & PgPool, id : Uuid, error_class : & str, last_error : & str,) -> Result <SendJob, SendJobError>
async fn bump_for_retry(pool : & PgPool, id : Uuid, next_attempt_at : DateTime <Utc>,) -> Result <SendJob, SendJobError>
async fn fail_and_retry(pool : & PgPool, id : Uuid, error_class : & str, last_error : & str,) -> Result <SendJob, SendJobError>
async fn list_jobs(pool : & PgPool, filter : ListJobsFilter,) -> Result <Vec <SendJobRow>, SendJobError>

ListJobsFilter

Messaging service (Sprint 44).

Item
pub struct ListJobsFilter

SendJobRow

Messaging service (Sprint 44).

Item
pub struct SendJobRow
async fn latest_for_destination(pool : & PgPool, destination : & str,) -> Result <Option <SendJobRow>, SendJobError>

services::messaging_credentials (other)

Messaging credentials service (Sprint 50).

Item
pub const KNOWN_PROVIDERS: & & str
async fn test_smtp_connection(pool : & PgPool, tenant_id : Uuid, probe_to : Option <& str>,) -> Result <TestSmtpResult, MessagingSettingsError>

MessagingSettingsError:MessagingSettingsError

Messaging credentials service (Sprint 50).

Item
pub enum MessagingSettingsError

MessagingSettingsError:delete

Messaging credentials service (Sprint 50).

Item
async fn delete_tenant_provider_config(pool : & PgPool, tenant_id : Uuid, provider_kind : & str,) -> Result <(), MessagingSettingsError>
async fn delete_system_provider_config(pool : & PgPool, provider_kind : & str,) -> Result <(), MessagingSettingsError>
async fn delete_tenant_smtp(pool : & PgPool, tenant_id : Uuid,) -> Result <(), MessagingSettingsError>
async fn delete_system_smtp(pool : & PgPool) -> Result <(), MessagingSettingsError>

MessagingSettingsError:from

Messaging credentials service (Sprint 50).

Item
MessagingSettingsError :: fn from(e : MessagingCredentialsError) -> Self

MessagingSettingsError:get

Messaging credentials service (Sprint 50).

Item
async fn get_tenant_provider_config(pool : & PgPool, tenant_id : Uuid, provider_kind : & str,) -> Result <Option <serde_json::Value>, MessagingSettingsError>
async fn get_system_provider_config(pool : & PgPool, provider_kind : & str,) -> Result <Option <serde_json::Value>, MessagingSettingsError>
async fn get_tenant_smtp(pool : & PgPool, tenant_id : Uuid,) -> Result <Option <SmtpConfig>, MessagingSettingsError>
async fn get_system_smtp(pool : & PgPool) -> Result <Option <SmtpConfig>, MessagingSettingsError>

MessagingSettingsError:list

Messaging credentials service (Sprint 50).

Item
async fn list_tenant_providers(pool : & PgPool, tenant_id : Uuid,) -> Result <Vec <MessagingProviderInfo>, MessagingSettingsError>
async fn list_system_providers(pool : & PgPool,) -> Result <Vec <MessagingProviderInfo>, MessagingSettingsError>

MessagingSettingsError:resolve

Messaging credentials service (Sprint 50).

Item
async fn resolve_smtp(pool : & PgPool, tenant_id : Uuid,) -> Result <Option <ResolvedConfig <SmtpConfig>>, MessagingSettingsError>

MessagingSettingsError:set

Messaging credentials service (Sprint 50).

Item
async fn set_tenant_provider_config(pool : & PgPool, tenant_id : Uuid, provider_kind : & str, body : serde_json::Value,) -> Result <(), MessagingSettingsError>
async fn set_system_provider_config(pool : & PgPool, provider_kind : & str, body : serde_json::Value,) -> Result <(), MessagingSettingsError>
async fn set_tenant_smtp(pool : & PgPool, tenant_id : Uuid, config : & SmtpConfig,) -> Result <(), MessagingSettingsError>
async fn set_system_smtp(pool : & PgPool, config : & SmtpConfig,) -> Result <(), MessagingSettingsError>

TestSmtpResult

Messaging credentials service (Sprint 50).

Item
pub struct TestSmtpResult

workers::messaging_worker (other)

Messaging worker loop (Sprint 46 + 51).

Item
fn default_messaging_registry() -> Arc <OutboundProviderRegistry>

MessagingWorkerConfig

Messaging worker loop (Sprint 46 + 51).

Item
pub struct MessagingWorkerConfig
MessagingWorkerConfig :: fn new(channel : SendChannel) -> Self
async fn run_messaging_worker(pool : PgPool, sender : Arc <dyn MessageSender>, config : MessagingWorkerConfig, shutdown : CancellationToken,)
async fn run_messaging_worker_with_registry(pool : PgPool, registry : Arc <OutboundProviderRegistry>, config : MessagingWorkerConfig, shutdown : CancellationToken,)

How to use it

No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.

Module structure

application_messaging

flowchart TD
  n_application_messaging["application_messaging"]
  n_application_messaging --> n_api["api"]
  n_api --> n_api__messaging_admin["messaging_admin"]
  n_api --> n_api__messaging_settings_admin["messaging_settings_admin"]
  n_application_messaging --> n_services["services"]
  n_services --> n_services__messaging["messaging"]
  n_services --> n_services__messaging_credentials["messaging_credentials"]
  n_application_messaging --> n_workers["workers"]
  n_workers --> n_workers__messaging_worker["messaging_worker"]
  n_workers --> n_workers__sender["sender"]

Public surface

`crate root`

ItemWhat it is
pub struct MessagingModule
MessagingModule :: fn new() -> Self
MessagingModule :: fn name(& self) -> & 'static str
MessagingModule :: fn version(& self) -> & 'static str
MessagingModule :: fn migrations(& self) -> Vec <MigrationSet>
MessagingModule :: async fn migrate_pending(& self, pool : & PgPool) -> Result <u32, MigrationError>

`api`

ItemWhat it is
fn admin_router <S>() -> Router <S> where S : Clone + Send + Sync + 'static, PgPool : FromRef <S>,Merge all admin-scoped messaging routers into one, generic over S.

`api::messaging_admin`

ItemWhat it is
fn router <S>() -> Router <S> where S : Clone + Send + Sync + 'static, PgPool : FromRef <S>,

`api::messaging_settings_admin`

ItemWhat it is
fn router <S>() -> Router <S> where S : Clone + Send + Sync + 'static, PgPool : FromRef <S>,

`services::messaging`

ItemWhat it is
fn default_next_attempt_at(attempt_count : i32) -> DateTime <Utc>Default retry backoff: returns the next attempt time for a given attempt count
async fn enqueue_send(pool : & PgPool, req : EnqueueRequest) -> Result <SendJob, SendJobError>
async fn get_send_job(pool : & PgPool, id : Uuid) -> Result <SendJob, SendJobError>
async fn claim_pending(pool : & PgPool, channel : SendChannel, limit : i64,) -> Result <Vec <SendJob>, SendJobError>
async fn mark_sent(pool : & PgPool, id : Uuid, provider_message_id : Option <& str>,) -> Result <SendJob, SendJobError>
async fn mark_failed(pool : & PgPool, id : Uuid, error_class : & str, last_error : & str,) -> Result <SendJob, SendJobError>
async fn bump_for_retry(pool : & PgPool, id : Uuid, next_attempt_at : DateTime <Utc>,) -> Result <SendJob, SendJobError>
async fn fail_and_retry(pool : & PgPool, id : Uuid, error_class : & str, last_error : & str,) -> Result <SendJob, SendJobError>Convenience: mark failed AND schedule a retry using the default backoff
pub struct ListJobsFilter
pub struct SendJobRow
async fn list_jobs(pool : & PgPool, filter : ListJobsFilter,) -> Result <Vec <SendJobRow>, SendJobError>
async fn latest_for_destination(pool : & PgPool, destination : & str,) -> Result <Option <SendJobRow>, SendJobError>

`services::messaging_credentials`

ItemWhat it is
pub const KNOWN_PROVIDERS: & & strThe list of provider kinds Sprint 0.19/52 ships builders for
pub enum MessagingSettingsError
MessagingSettingsError :: fn from(e : MessagingCredentialsError) -> Self
async fn set_tenant_provider_config(pool : & PgPool, tenant_id : Uuid, provider_kind : & str, body : serde_json::Value,) -> Result <(), MessagingSettingsError>
async fn set_system_provider_config(pool : & PgPool, provider_kind : & str, body : serde_json::Value,) -> Result <(), MessagingSettingsError>
async fn get_tenant_provider_config(pool : & PgPool, tenant_id : Uuid, provider_kind : & str,) -> Result <Option <serde_json::Value>, MessagingSettingsError>
async fn get_system_provider_config(pool : & PgPool, provider_kind : & str,) -> Result <Option <serde_json::Value>, MessagingSettingsError>
async fn delete_tenant_provider_config(pool : & PgPool, tenant_id : Uuid, provider_kind : & str,) -> Result <(), MessagingSettingsError>
async fn delete_system_provider_config(pool : & PgPool, provider_kind : & str,) -> Result <(), MessagingSettingsError>
async fn set_tenant_smtp(pool : & PgPool, tenant_id : Uuid, config : & SmtpConfig,) -> Result <(), MessagingSettingsError>
async fn get_tenant_smtp(pool : & PgPool, tenant_id : Uuid,) -> Result <Option <SmtpConfig>, MessagingSettingsError>
async fn delete_tenant_smtp(pool : & PgPool, tenant_id : Uuid,) -> Result <(), MessagingSettingsError>
async fn set_system_smtp(pool : & PgPool, config : & SmtpConfig,) -> Result <(), MessagingSettingsError>
async fn get_system_smtp(pool : & PgPool) -> Result <Option <SmtpConfig>, MessagingSettingsError>
async fn delete_system_smtp(pool : & PgPool) -> Result <(), MessagingSettingsError>
async fn list_tenant_providers(pool : & PgPool, tenant_id : Uuid,) -> Result <Vec <MessagingProviderInfo>, MessagingSettingsError>
async fn list_system_providers(pool : & PgPool,) -> Result <Vec <MessagingProviderInfo>, MessagingSettingsError>
async fn resolve_smtp(pool : & PgPool, tenant_id : Uuid,) -> Result <Option <ResolvedConfig <SmtpConfig>>, MessagingSettingsError>Used by the messaging worker / outbound senders when about to dispatch on behalf of a tenant
pub struct TestSmtpResult
async fn test_smtp_connection(pool : & PgPool, tenant_id : Uuid, probe_to : Option <& str>,) -> Result <TestSmtpResult, MessagingSettingsError>Resolve the SMTP config + construct a sender + attempt to send a tiny probe message to a caller-supplied address

`workers::messaging_worker`

ItemWhat it is
fn default_messaging_registry() -> Arc <OutboundProviderRegistry>Build a default registry pre-registered with the SMTP builder
pub struct MessagingWorkerConfig
MessagingWorkerConfig :: fn new(channel : SendChannel) -> Self
async fn run_messaging_worker(pool : PgPool, sender : Arc <dyn MessageSender>, config : MessagingWorkerConfig, shutdown : CancellationToken,)Run the messaging worker until the cancellation token fires
async fn run_messaging_worker_with_registry(pool : PgPool, registry : Arc <OutboundProviderRegistry>, config : MessagingWorkerConfig, shutdown : CancellationToken,)Run the messaging worker with per-tenant credential resolution via the upstream OutboundProviderRegistry

Re-exports. Exported here, defined elsewhere.

ExportDefined in
SendJobStatusinfrastructure_communication::SendJobStatus
{AlwaysFailOutboundSenderasAlwaysFailSender,OutboundErrorasSenderError,OutboundOutcomeasSendOutcome,OutboundSenderasMessageSender,StubOutboundSenderasStubMessageSender,}infrastructure_communication::{AlwaysFailOutboundSenderasAlwaysFailSender,OutboundErrorasSenderError,OutboundOutcomeasSendOutcome,OutboundSenderasMessageSender,StubOutboundSenderasStubMessageSender,}

Boundary

Reaches into infrastructure, platform.

Shares tier application with 120 other crates: application-agreements, application-ai, application-analytics, application-approvals, application-assessments, application-audit-log, application-auth, application-billing, … (120 total).

_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._

Where it sits

Tier (ontology)application
Architectural role (taxonomy)unclassified (baselined)
Locationcrates/application/messaging
Vocabulary in force (lexicon)current

Tier flow. Which tiers this crate's own edges cross.

flowchart LR
  n_application["application"] --> n_infrastructure["infrastructure"]
  n_application["application"] --> n_platform["platform"]

Dependencies

Runtime, in this workspace.

CrateTierOptionalOnly on
`application-core`applicationnoalways
`application-rbac`applicationnoalways
`infrastructure-ai`infrastructurenoalways
`infrastructure-communication`infrastructurenoalways
`platform-dto`platformnoalways

Runtime, from outside the workspace.

CrateRequirementFeaturesOptionalOnly on
async-trait^0.1noalways
axum^0.7multipartnoalways
chrono^0.4serdenoalways
futures-util^0.3noalways
serde^1derivenoalways
serde_json^1noalways
sqlx^0.8runtime-tokio, postgres, chrono, uuid, jsonnoalways
thiserror^2noalways
tokio^1fullnoalways
tokio-util^0.7rtnoalways
tracing^0.1noalways
uuid^1v4, v7, serde, jsnoalways

Development, in this workspace.

CrateTierOptionalOnly on
`application-engine`applicationnoalways
`application-parties`applicationnoalways

Build. None.

Depended on by. Nothing in this workspace.

Signal flow — what reaches this crate, and what it reaches.

flowchart LR
  SELF["application-messaging"]
  SELF -->|development| n_application_engine["application-engine"]
  SELF -->|development| n_application_parties["application-parties"]
  SELF -->|runtime| n_application_core["application-core"]
  SELF -->|runtime| n_application_rbac["application-rbac"]
  SELF -->|runtime| n_infrastructure_ai["infrastructure-ai"]
  SELF -->|runtime| n_infrastructure_communication["infrastructure-communication"]
  SELF -->|runtime| n_platform_dto["platform-dto"]
  classDef self fill:#1f883d,stroke:#1f883d,color:#fff;
  class SELF self;

Feature flags

No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.

Targets

KindNameSource
libapplication_messaging`src/lib.rs`
testpg_forge_messaging`tests/pg_forge_messaging.rs`

Error model

Error typeNamed by
MessagingSettingsErrordelete_system_provider_config, delete_system_smtp, delete_tenant_provider_config, delete_tenant_smtp, get_system_provider_config, get_system_smtp, … (16 total)

Operational characteristics

PropertyEvidence
async public surfaceyes
async runtimeyes
database accessyes
network I/Oyes
unsafe codenone detected
environment variablesyes

No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.

Configuration

VariableRead in
CARGO_PKG_VERSIONsrc/lib.rs

No workspace crate depends on this one.

Verification

KindCount
Unit tests7
Integration tests1
Examples0
Doctests0

Evidence by module. How often each public module is named by something executable.

ModuleTestsExamplesConsumers
crate root100
api100
api::messaging_admin100
api::messaging_settings_admin100
services::messaging1200
services::messaging_credentials1900
workers::messaging_worker400

What the tests establish, by name:

Documentation coverage

MeasureDocumentedTotal
Public items with rustdoc946
Public modules with a //! block79
pie showData
    title Public items with rustdoc
    "Documented" : 9
    "No rustdoc detected" : 37

Metrics

MetricValue
Rust source files10
Source lines1407
Code lines1041
Public API items46
Public modules9
Tests8
Examples0
Cargo features0
Direct runtime dependencies17
Workspace reverse dependencies0
pie showData
    title Public API by kind
    "constant" : 1
    "enum" : 1
    "function" : 32
    "method" : 7
    "struct" : 5
pie showData
    title Rust source composition
    "Code" : 1041
    "Blank or comment" : 366

Generation

Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.

Todas las application · Manual