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.
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
- Self-hosted Claspine in your app (Add self-hosted Claspine to an existing app) or the engine (Run the Claspine engine).
- A provider account: Amazon SES (guide) or an SMTP service (guide) for email; Amazon SNS or Twilio for SMS; a WhatsApp Business Account in AWS (guide); a Firebase project for push.
Steps
- Turn on a provider with its settings (table below). Spring Boot: in
application.yml, for exampleplatform.smtp.enabled: true. Engine: the same names as environment variables, for examplePLATFORM_SMTP_ENABLED=true,PLATFORM_SMTP_SENDEREMAIL=orders@shop.com. Keep passwords in environment variables or your secret store, never in the file. - 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_IDandAWS_SECRET_ACCESS_KEYenvironment variables, with a key that may only send. - Write your templates in a YAML file (snippet). Spring Boot:
src/main/resources/notifications/<app>-notifications.yml. Engine (0.31.0 and later): any.ymlfile in the folder mounted at/config/notifications. Placeholders are{name}; list every placeholder undervariables. Email templates have asubject. The log showsLoaded notification configuration from …: 1 templatesat start. - Send to an address. Spring Boot: inject
DirectMessagesand callsend(new SendDirectMessageCommand(scope, channel, to, templateKey, variables, idempotencyKey, actorId, correlationId)); the scope isnew NotificationScope(applicationBootstrap.current().productId(), Optional.empty()). Engine:POST /api/v1/messageswith your service key and anIdempotency-Keyheader, orclaspine.messages.send({ channel, to, template, variables })in Node. The service key needs themessagesscope: addmessagestoPLATFORM_ENGINE_SERVICEKEYS_0_SCOPES. - Check the result. The answer has a
status:ACCEPTED(the provider took it),RETRYABLE_FAILURE(Claspine retries for up to 72 hours) orTERMINAL_FAILUREwithfailureDetail. Read one message withGET /api/v1/messages/{id}(orDirectMessages.find) and recent ones withGET /api/v1/messages. The same idempotency key never sends twice. - Set a history key:
platform.messages.recipient-hash-key(engine:PLATFORM_MESSAGES_RECIPIENT_HASH_KEY), a random secret such as the output ofopenssl 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. - Optional, to a person by type (Spring Boot): declare the type under
definitionsin the same YAML (type, channels, one template per channel), register where each person can be reached withEndpointRegistry.registerwhen 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 callNotificationService.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). - 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(sesorsmtp) orsms-provider(snsortwilio). WhatsApp codes need a Meta-approved authentication template:platform.notifications.sign-in-codes.whatsapp-template. - 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 atlocalhostport 1025 withsecurity: NONE(allowed only for a server on your own machine); messages appear at http://localhost:8025.
# 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 field | Value |
|---|---|
| Email: Amazon SES | platform.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: SMTP | platform.smtp.enabled, host, port (587, 465 or 2525), security (STARTTLS default, SSL), username, password, sender-email, sender-name. |
| SMS: Amazon SNS | platform.sns.enabled, region, sender-id (optional, 6–11 letters), entity-id (India DLT, optional). |
| SMS: Twilio | platform.twilio.enabled, account-sid, auth-token, sender-phone. |
| WhatsApp: AWS End User Messaging | platform.whatsapp.enabled, region, origination-phone-number-id (the phone number ID shown in AWS). |
| Push: Firebase Cloud Messaging | platform.fcm.enabled, project-id; credentials from GOOGLE_APPLICATION_CREDENTIALS or the server’s Google identity. Push is sent with NotificationService (Spring Boot). |
| Which provider sends | With 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 variables | The 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.