SETUP GUIDES · MESSAGING

Notifications in self-hosted Claspine (email, SMS, WhatsApp, push)

Connect your own email, SMS, WhatsApp or push provider, write templates, and send from your code: Spring Boot or the engine.

⏱ About 20 minutes once your provider account is ready

Self-hosted Claspine sends through your own provider accounts: email through Amazon SES or any SMTP service, SMS through Amazon SNS or Twilio, WhatsApp through AWS End User Messaging, push through Firebase Cloud Messaging. You turn a provider on with settings, write your messages as templates in a YAML file, and send from your code; Claspine renders the template, sends, retries and records what happened. There are two ways to send. To an address (DirectMessages, or POST /api/v1/messages on the engine): you pass an email address or phone number your app already knows; works with any login, including your own. To a person by notification type (NotificationService, Spring Boot): you pass a Claspine account and a type such as order.shipped, and Claspine picks the channels and addresses it has for that person; this is also the way to send push. Start with “to an address” unless you need push.

Words you’ll see

Message template (WhatsApp)
A message text Meta approved in advance, with numbered blanks {{1}}, {{2}}. WhatsApp only lets businesses start a conversation with an approved template.
SMTP
The standard way programs hand email to a mail service. Your mail service gives you a host, a port, a username and a password (often an API key) for it.
DKIM
A signature on every email that proves it really comes from your domain. You turn it on by adding the DNS records SES gives you; without it, mail lands in spam.

Before you start

Steps

  1. Turn on a provider with its settings (table below). Spring Boot: in application.yml, for example platform.smtp.enabled: true. Engine: the same names as environment variables, for example PLATFORM_SMTP_ENABLED=true, PLATFORM_SMTP_SENDEREMAIL=orders@shop.com. Keep passwords in environment variables or your secret store, never in the file.
  2. AWS providers (SES, SNS, WhatsApp) need no keys in production on AWS: give the server an IAM role. Elsewhere use the standard AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables, with a key that may only send.
  3. Write your templates in a YAML file (snippet). Spring Boot: src/main/resources/notifications/<app>-notifications.yml. Engine (0.31.0 and later): any .yml file in the folder mounted at /config/notifications. Placeholders are {name}; list every placeholder under variables. Email templates have a subject. The log shows Loaded notification configuration from …: 1 templates at start.
  4. Send to an address. Spring Boot: inject DirectMessages and call send(new SendDirectMessageCommand(scope, channel, to, templateKey, variables, idempotencyKey, actorId, correlationId)); the scope is new NotificationScope(applicationBootstrap.current().productId(), Optional.empty()). Engine: POST /api/v1/messages with your service key and an Idempotency-Key header, or claspine.messages.send({ channel, to, template, variables }) in Node. The service key needs the messages scope: add messages to PLATFORM_ENGINE_SERVICEKEYS_0_SCOPES.
  5. Check the result. The answer has a status: ACCEPTED (the provider took it), RETRYABLE_FAILURE (Claspine retries for up to 72 hours) or TERMINAL_FAILURE with failureDetail. Read one message with GET /api/v1/messages/{id} (or DirectMessages.find) and recent ones with GET /api/v1/messages. The same idempotency key never sends twice.
  6. Set a history key: platform.messages.recipient-hash-key (engine: PLATFORM_MESSAGES_RECIPIENT_HASH_KEY), a random secret such as the output of openssl rand -hex 32. Claspine keeps the full address only until a message is finished (72 hours at most); with this key the history can still find “messages to this address” without storing it.
  7. Optional, to a person by type (Spring Boot): declare the type under definitions in the same YAML (type, channels, one template per channel), register where each person can be reached with EndpointRegistry.register when they verify an address or install your app (email: notification-endpoint://email- plus the base64url address; phone: notification-endpoint://phone- plus the digits; push: notification-endpoint:// plus the FCM token), then call NotificationService.send(new SendNotificationCommand(scope, accountId, "order.shipped", variables, Optional.empty(), idempotencyKey, actorId, correlationId, Optional.empty())). The account must exist in Claspine (people who signed in with Claspine do).
  8. Sign-in codes (one-time codes by email, SMS or WhatsApp) use the same providers. When two providers serve one channel, choose with platform.notifications.sign-in-codes.email-provider (ses or smtp) or sms-provider (sns or twilio). WhatsApp codes need a Meta-approved authentication template: platform.notifications.sign-in-codes.whatsapp-template.
  9. Test without sending real email: run a local mail catcher such as Mailpit (docker run -p 1025:1025 -p 8025:8025 axllent/mailpit) and point SMTP at localhost port 1025 with security: NONE (allowed only for a server on your own machine); messages appear at http://localhost:8025.
A template file, email over SMTP, and sending from Spring Boot and from any language through the engine.
# src/main/resources/notifications/shop-notifications.yml (engine: ./notifications/messages.yml, mounted at /config/notifications)
product: shop
templates:
  - key: order.shipped
    channel: EMAIL
    locale: en
    subject: "Order {order} has shipped"
    body: "Hi {name}, your order {order} is on its way."
    variables: [name, order]

# application.yml (engine: PLATFORM_SMTP_ENABLED=true, PLATFORM_SMTP_HOST=..., and so on)
platform:
  smtp:
    enabled: true
    host: smtp.sendgrid.net        # or email-smtp.<region>.amazonaws.com, smtp.office365.com ...
    port: 587                      # STARTTLS; 465 for SSL
    username: apikey
    password: ${SMTP_PASSWORD}
    sender-email: orders@shop.com
    sender-name: Shop
  messages:
    recipient-hash-key: ${MESSAGES_HASH_KEY}

// Spring Boot
DirectMessage message = directMessages.send(new SendDirectMessageCommand(
        new NotificationScope(applicationBootstrap.current().productId(), Optional.empty()),
        NotificationChannel.EMAIL, "customer@example.com", "order.shipped",
        Map.of("name", "Ravi", "order", "A-42"),
        new IdempotencyKey("order-A-42-shipped"),           // letters, digits, . _ : - only
        new ActorId(userId), new CorrelationId(UUID.randomUUID().toString())));
// message.status(): ACCEPTED, RETRYABLE_FAILURE (retried) or TERMINAL_FAILURE

# Engine (any language): the service key needs the messages scope
curl -X POST http://localhost:8080/api/v1/messages \
  -H "X-Spine-Api-Key: $SPINE_API_KEY" -H "X-Spine-Host: localhost" \
  -H "Idempotency-Key: order-A-42-shipped" -H "Content-Type: application/json" \
  -d '{"channel":"EMAIL","to":"customer@example.com","template":"order.shipped","variables":{"name":"Ravi","order":"A-42"}}'

What goes where in Claspine

Console fieldValue
Email: Amazon SESplatform.ses.enabled, region, sender-email (a verified address or domain). Keys from the IAM role or AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY.
Email: SMTPplatform.smtp.enabled, host, port (587, 465 or 2525), security (STARTTLS default, SSL), username, password, sender-email, sender-name.
SMS: Amazon SNSplatform.sns.enabled, region, sender-id (optional, 6–11 letters), entity-id (India DLT, optional).
SMS: Twilioplatform.twilio.enabled, account-sid, auth-token, sender-phone.
WhatsApp: AWS End User Messagingplatform.whatsapp.enabled, region, origination-phone-number-id (the phone number ID shown in AWS).
Push: Firebase Cloud Messagingplatform.fcm.enabled, project-id; credentials from GOOGLE_APPLICATION_CREDENTIALS or the server’s Google identity. Push is sent with NotificationService (Spring Boot).
Which provider sendsWith two providers for one channel: platform.messages.providers.email (ses or smtp), .sms (sns or twilio), .whatsapp. Otherwise SES before SMTP and SNS before Twilio.
Engine environment variablesThe same names in capitals with dots and dashes removed: platform.smtp.sender-email becomes PLATFORM_SMTP_SENDEREMAIL.

Keep it safe

  • Send only from your backend. Browsers and mobile apps never get the service key or provider passwords.
  • Give AWS keys only the send permissions in the provider guides, and prefer an IAM role in production.
  • Message people who expect it: transactional messages, or marketing only with the consent the recipient’s country requires and a working opt-out.
  • Use a stable idempotency key per real-world event (for example order-A-42-shipped), so a retry in your code never sends twice.

Claspine stores the values encrypted, never shows them again in full, and never sends them to your app or SDK.

If something goes wrong

409 MESSAGING_NOT_CONFIGURED.
No provider is turned on for that channel. Set platform.<provider>.enabled: true (engine: PLATFORM_<PROVIDER>_ENABLED=true) with its settings and restart.
404 MESSAGE_TEMPLATE_NOT_FOUND.
No template with that key and channel was loaded. Check the file location (Spring Boot: src/main/resources/notifications/; engine 0.31.0+: the folder at /config/notifications) and the start-up log line “Loaded notification configuration”.
400 MESSAGE_VARIABLES_MISSING.
The template lists a variable you did not pass; send every name under variables.
The engine answers 403 SERVICE_KEY_SCOPE_MISSING.
The service key lacks the messages scope: add messages to PLATFORM_ENGINE_SERVICEKEYS_<n>_SCOPES and restart the engine.
ACCEPTED but nothing arrives.
Amazon SES and SNS in sandbox mode deliver only to verified addresses and numbers; request production access. For SMTP, check the sender is allowed by your mail service and SPF/DKIM are set for its domain.
SMTP refused for “security NONE”.
Unencrypted SMTP is allowed only for a server on your own machine (testing). Use STARTTLS (587) or SSL (465) for real providers.

← All setup guides