Passo a passo para uma aplicação iGRP enviar notificações — template, envio a partir do backend Spring Boot ou do frontend Next.js, inbox e sino da header.
Este guia liga uma aplicação iGRP ao Notification Service, do template ao sino da header. O exemplo envia por email a confirmação de um pedido; troca os nomes pelos da tua aplicação.
Os SDKs têm páginas de referência próprias: SDK TypeScript e SDK Java.
| Quem envia | Quando usar |
|---|---|
| Backend (Spring Boot + SDK Java) | O envio é consequência de uma regra de negócio do serviço (convites, alterações de estado). |
| Frontend (Server Action + SDK TypeScript) | O envio parte de uma ação do utilizador na app (fechar um passo, emitir um recibo). |
Nos dois casos o envio é M2M: um cliente OAuth2 client_credentials do Access Management. A inbox, essa, lê-se com o token do utilizador.
Processos já notificam
O runtime de processos avisa por IN_APP quem pode executar a tarefa seguinte, sem código na tua app — ver Notificações de tarefas.
1. Pré-requisitos
- Um cliente M2M no Access Management (client ID + secret) cuja conta de serviço:
- está associada à aplicação, para o token sair com o claim
application_code; - tem a permissão
igrp.notification.send(eigrp.notification.readpara consultar envios;igrp.notification.adminse também listares templates).
- está associada à aplicação, para o token sair com o claim
- O código da aplicação (
IGRP_APP_CODE). - O canal que vais usar ligado no serviço (para EMAIL, o SMTP — ver Configurar os canais) e ativo nas definições da aplicação. Senão:
422 CHANNEL_NOT_ENABLED.
Conta de serviço sem aplicação
Se o token M2M não trouxer application_code, o serviço tenta validar o acesso à aplicação como se o chamador fosse um utilizador, e uma conta de serviço falha: 422 ACCESS_MANAGER_UNAVAILABLE (User not found). Sem permissões, a resposta é 403. Confirma os dois no Access Management antes de depurar o código.
2. Criar e publicar o template
Cria o template na Notifications UI (editor visual) ou pela API de templates e publica-o: só templates publicados são usados no envio.
| Campo | Exemplo |
|---|---|
| Código | pedido-confirmacao |
| Canal | EMAIL |
| Locale | pt-CV |
| Assunto | Pedido {{numeroPedido}} recebido |
| Variáveis | nome, numeroPedido |
As variáveis usam a sintaxe Handlebars ({{nome}}). O envio procura o template com o locale exato: se só publicaste pt-CV, um envio com pt falha com 422 TEMPLATE_ERROR. Detalhes em Templates.
Backend (Spring Boot)
3. Dependência e cliente
Adiciona cv.igrp.platform.notification:client e cria os beans ApiClient e NotificationsApi como em SDK Java › Configurar num serviço Spring Boot.
4. Serviço de envio
EMAIL precisa de SMTP configurado
Para enviar por EMAIL, o provider SMTP tem de estar configurado no Notification Service (NOTIFICATION_PROVIDERS_EMAILSMTP_*: servidor, porta, credenciais e remetente). Sem ele, o envio é recusado com 422 CHANNEL_NOT_ENABLED e nenhum email sai — ver Configurar os canais › EMAIL (SMTP). O canal também tem de estar ativo nas Definições da aplicação.
@Service
public class ConfirmacaoNotifier {
private static final Logger LOG = LoggerFactory.getLogger(ConfirmacaoNotifier.class);
private final Optional<NotificationsApi> notificationsApi;
public ConfirmacaoNotifier(Optional<NotificationsApi> notificationsApi) {
this.notificationsApi = notificationsApi;
}
public void enviar(String email, String nome, String numeroPedido) {
if (notificationsApi.isEmpty()) {
LOG.info("Notificações desativadas, confirmação {} não enviada", numeroPedido);
return;
}
TemplateRefDto template = new TemplateRefDto();
template.setCode("pedido-confirmacao");
template.setLocale("pt-CV");
RecipientDto recipient = new RecipientDto();
recipient.setEmail(email);
SendNotificationRequest request = new SendNotificationRequest();
request.setChannelStrategy(ChannelStrategy.SPECIFIC);
request.setChannels(List.of(Channel.EMAIL));
request.setRecipients(List.of(recipient));
request.setTemplate(template);
request.setVariables(Map.of("nome", nome, "numeroPedido", numeroPedido));
request.setBusinessKey(numeroPedido);
request.setIdempotencyKey("pedido-confirmacao:" + numeroPedido);
try {
notificationsApi.get().send(request);
} catch (ApiException e) {
// o envio falhou; decide se isto deve falhar o comando de negócio
LOG.warn("Confirmação {} não enviada: {} {}", numeroPedido, e.getErrorCode(), e.getErrorMessage());
}
}
}idempotencyKey evita emails duplicados se o comando for repetido.
Frontend (Next.js)
3. Instalar o SDK
pnpm add @igrp/platform-notification-client-ts4. Factories do cliente
Um cliente M2M (criado uma vez) para enviar, e um cliente por pedido com o token do utilizador para a inbox:
// src/lib/notification-client.ts
import 'server-only';
import { NotificationClient } from '@igrp/platform-notification-client-ts';
import { auth } from '@/lib/auth';
const baseUrl = process.env.NOTIFICATION_SERVICE_BASE_URL ?? '';
let m2mClient: NotificationClient | undefined;
/** Envio, anexos, templates — token client_credentials do Access Management. */
export function getNotificationM2MClient(): NotificationClient {
m2mClient ??= NotificationClient.create(
{ baseUrl },
{
clientId: process.env.IGRP_M2M_CLIENT_ID!,
clientSecret: process.env.IGRP_M2M_CLIENT_SECRET!,
authBaseUrl: process.env.IGRP_ACCESS_MANAGEMENT_API || process.env.IGRP_AUTH_ISSUER!,
},
);
return m2mClient;
}
/** Inbox do utilizador autenticado — o userId sai do token. */
export async function getNotificationUserClient(): Promise<NotificationClient> {
const token = await auth.getAccessToken();
if (!token?.accessToken) throw new Error('Sem sessão');
return NotificationClient.create({
baseUrl,
timeout: 30000,
headers: { Authorization: `Bearer ${token.accessToken}` },
});
}5. Server Action de envio
EMAIL precisa de SMTP configurado
Para enviar por EMAIL, o provider SMTP tem de estar configurado no Notification Service (NOTIFICATION_PROVIDERS_EMAILSMTP_*: servidor, porta, credenciais e remetente). Sem ele, o envio é recusado com 422 CHANNEL_NOT_ENABLED e nenhum email sai — ver Configurar os canais › EMAIL (SMTP). O canal também tem de estar ativo nas Definições da aplicação.
// src/app/(minha-app)/client/notification.ts
'use server';
import {
ApiClientError,
Channel,
ChannelStrategy,
IgrpNotificationErrorCode,
type SendNotificationRequest,
} from '@igrp/platform-notification-client-ts';
import { getNotificationM2MClient } from '@/lib/notification-client';
export async function enviarConfirmacao(input: { email: string; nome: string; numeroPedido: string }) {
const client = getNotificationM2MClient();
const base: SendNotificationRequest = {
applicationCode: process.env.IGRP_APP_CODE!,
channelStrategy: ChannelStrategy.SPECIFIC,
channels: [Channel.EMAIL],
recipients: [{ email: input.email }],
category: 'pedido-confirmacao',
businessKey: input.numeroPedido,
idempotencyKey: `pedido-confirmacao-${input.numeroPedido}`,
};
try {
const { data } = await client.notifications.send({
...base,
template: { code: 'pedido-confirmacao', locale: 'pt-CV' },
variables: { nome: input.nome, numeroPedido: input.numeroPedido },
});
return data.id;
} catch (error) {
if (!(error instanceof ApiClientError)) throw error;
const semTemplate =
error.is(IgrpNotificationErrorCode.TEMPLATE_NOT_FOUND) || error.is(IgrpNotificationErrorCode.TEMPLATE_ERROR);
if (!semTemplate) {
// Server Actions só passam `message` ao browser
throw new Error(`${error.serverMessage ?? error.message} [${error.errorCode ?? error.status}]`);
}
// template ainda não publicado: envia o mesmo texto inline
const { data } = await client.notifications.send({
...base,
inline: {
subject: `Pedido ${input.numeroPedido} recebido`,
title: 'Pedido recebido',
body: `Olá ${input.nome}, recebemos o pedido ${input.numeroPedido}.`,
},
});
return data.id;
}
}Para enviar um PDF, faz primeiro client.attachments.upload(blob, 'recibo.pdf', process.env.IGRP_APP_CODE) e junta attachments: [{ filename: 'recibo.pdf', contentRef }] — ver Anexos.
6. Sino de notificações na header
A header do framework mostra o array notifications devolvido por getHeaderData() em src/igrp.template.config.ts. Lê a inbox do utilizador e converte cada item com o mapInboxItem de SDK TypeScript › Sino da header:
// src/lib/header-notifications.ts
import 'server-only';
import type { IGRPNotificationArgs } from '@igrp/framework-next-types';
import { getNotificationUserClient } from '@/lib/notification-client';
import { mapInboxItem } from '@/lib/map-inbox-notification';
export async function getHeaderNotifications(): Promise<IGRPNotificationArgs[]> {
try {
const client = await getNotificationUserClient();
const { data } = await client.inbox.list({ unreadOnly: false }, { page: 0, size: 10 });
return (data.content ?? []).map(mapInboxItem);
} catch {
return []; // o sino nunca deve partir a página
}
}// src/igrp.template.config.ts (excerto)
getHeaderData: async () => {
const user = bypass ? getMockUser().mockUser : undefined;
return {
user,
notifications: bypass ? [] : await getHeaderNotifications(),
notificationsUrl: process.env.NEXT_PUBLIC_IGRP_NOTIFICATION_URL || '',
showNotifications: true,
// ... restantes flags
};
},Para marcar como lida, chama inbox.markRead(deliveryId) (o id do item mapeado) numa Server Action.
Variáveis de ambiente
Backend
| Variável | Descrição |
|---|---|
NOTIFICATION_SERVICE_BASE_URL | Base do Notification Service, sem /api/v1 |
IGRP_ACCESS_MANAGEMENT_API | Base do Access Management (/oauth2/token) |
IGRP_M2M_CLIENT_ID, IGRP_M2M_CLIENT_SECRET | Cliente M2M |
Frontend
| Variável | Descrição |
|---|---|
NOTIFICATION_SERVICE_BASE_URL | Base do Notification Service, sem /api/v1 |
IGRP_APP_CODE | Código da aplicação (applicationCode) |
IGRP_M2M_CLIENT_ID, IGRP_M2M_CLIENT_SECRET | Cliente M2M — só no servidor |
IGRP_ACCESS_MANAGEMENT_API (ou IGRP_AUTH_ISSUER) | authBaseUrl do token M2M |
NEXT_PUBLIC_IGRP_NOTIFICATION_URL | Página "ver todas" do sino |
No workspace do Studio o serviço fica em http://<slug>:<porta>/gateway-api/igrp-notification-service.
Última atualização:
