diff --git a/lib/custom-sms.js b/lib/custom-sms.js
new file mode 100644
index 00000000..3ba6d9c0
--- /dev/null
+++ b/lib/custom-sms.js
@@ -0,0 +1,17 @@
+const uuid = require('uuid')
+const db = require('./db')
+
+const getCustomMessages = () => {
+ const sql = `SELECT * FROM custom_messages`
+ return db.any(sql)
+}
+
+const createCustomMessage = (event, deviceId, message) => {
+ const sql = `INSERT INTO custom_message (event, device_id, message) VALUES ($2, $3, $4)`
+ return db.none(sql, [uuid.v4(), event, deviceId, message])
+}
+
+module.exports = {
+ getCustomMessages,
+ createCustomMessage
+}
diff --git a/lib/new-admin/graphql/resolvers/index.js b/lib/new-admin/graphql/resolvers/index.js
index 033593d7..f93c2bc5 100644
--- a/lib/new-admin/graphql/resolvers/index.js
+++ b/lib/new-admin/graphql/resolvers/index.js
@@ -15,6 +15,7 @@ const pairing = require('./pairing.resolver')
const rates = require('./rates.resolver')
const scalar = require('./scalar.resolver')
const settings = require('./settings.resolver')
+const sms = require('./sms.resolver')
const status = require('./status.resolver')
const transaction = require('./transaction.resolver')
const user = require('./users.resolver')
@@ -36,6 +37,7 @@ const resolvers = [
rates,
scalar,
settings,
+ sms,
status,
transaction,
user,
diff --git a/lib/new-admin/graphql/resolvers/sms.resolver.js b/lib/new-admin/graphql/resolvers/sms.resolver.js
new file mode 100644
index 00000000..6936dfe8
--- /dev/null
+++ b/lib/new-admin/graphql/resolvers/sms.resolver.js
@@ -0,0 +1,12 @@
+const customSms = require('../../../custom-sms')
+
+const resolvers = {
+ Query: {
+ customMessages: () => customSms.getCustomMessages()
+ },
+ Mutation: {
+ createCustomMessage: (...[, { event, deviceId, message }]) => customSms.createCustomMessage(event, deviceId, message)
+ }
+}
+
+module.exports = resolvers
diff --git a/lib/new-admin/graphql/types/index.js b/lib/new-admin/graphql/types/index.js
index 8745bdb3..e24322ef 100644
--- a/lib/new-admin/graphql/types/index.js
+++ b/lib/new-admin/graphql/types/index.js
@@ -15,6 +15,7 @@ const pairing = require('./pairing.type')
const rates = require('./rates.type')
const scalar = require('./scalar.type')
const settings = require('./settings.type')
+const sms = require('./sms.type')
const status = require('./status.type')
const transaction = require('./transaction.type')
const user = require('./users.type')
@@ -36,6 +37,7 @@ const types = [
rates,
scalar,
settings,
+ sms,
status,
transaction,
user,
diff --git a/lib/new-admin/graphql/types/sms.type.js b/lib/new-admin/graphql/types/sms.type.js
new file mode 100644
index 00000000..c6528b54
--- /dev/null
+++ b/lib/new-admin/graphql/types/sms.type.js
@@ -0,0 +1,25 @@
+const { gql } = require('apollo-server-express')
+
+const typeDef = gql`
+ type CustomMessage {
+ id: ID!
+ event: CustomMessageEvent!
+ deviceId: String
+ message: String!
+ }
+
+ enum CustomMessageEvent {
+ smsCode
+ cashOutDispenseReady
+ }
+
+ type Query {
+ customMessages: [CustomMessage] @auth
+ }
+
+ type Mutation {
+ createCustomMessage(event: CustomMessageEvent!, deviceId: String, message: String!): CustomMessage @auth
+ }
+`
+
+module.exports = typeDef
diff --git a/migrations/1627518944902-custom-sms.js b/migrations/1627518944902-custom-sms.js
new file mode 100644
index 00000000..ff19c1c5
--- /dev/null
+++ b/migrations/1627518944902-custom-sms.js
@@ -0,0 +1,20 @@
+var db = require('./db')
+
+exports.up = function (next) {
+ var sql = [
+ `CREATE TYPE custom_message_event AS ENUM('sms_code', 'cash_out_dispense_ready')`,
+ `CREATE TABLE custom_messages (
+ id UUID PRIMARY KEY,
+ event custom_message_event NOT NULL,
+ device_id TEXT REFERENCES devices(device_id),
+ message TEXT NOT NULL
+ )`,
+ `CREATE UNIQUE INDEX uq_custom_message_per_device ON custom_messages (event, device_id)`
+ ]
+
+ db.multi(sql, next)
+}
+
+exports.down = function (next) {
+ next()
+}
diff --git a/new-lamassu-admin/src/pages/OperatorInfo/CustomSMS/CustomSMS.js b/new-lamassu-admin/src/pages/OperatorInfo/CustomSMS/CustomSMS.js
new file mode 100644
index 00000000..188e7081
--- /dev/null
+++ b/new-lamassu-admin/src/pages/OperatorInfo/CustomSMS/CustomSMS.js
@@ -0,0 +1,127 @@
+import { useQuery } from '@apollo/react-hooks'
+import { makeStyles, Box } from '@material-ui/core'
+import gql from 'graphql-tag'
+import * as R from 'ramda'
+import React, { useState } from 'react'
+
+import { Link, IconButton } from 'src/components/buttons'
+import DataTable from 'src/components/tables/DataTable'
+import { H4 } from 'src/components/typography'
+import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg'
+import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
+
+import { global } from '../OperatorInfo.styles'
+
+import CustomSMSModal from './CustomSMSModal'
+
+const useStyles = makeStyles(global)
+
+const GET_CUSTOM_MESSAGES = gql`
+ query customMessages {
+ customMessages {
+ id
+ event
+ deviceId
+ message
+ }
+ }
+`
+
+const GET_MACHINES = gql`
+ {
+ machines {
+ name
+ deviceId
+ }
+ }
+`
+
+const CustomSMS = () => {
+ const classes = useStyles()
+
+ const [showModal, setShowModal] = useState(false)
+ const { data: messagesData, loading: messagesLoading } = useQuery(
+ GET_CUSTOM_MESSAGES
+ )
+ const { data: machinesData, loading: machinesLoading } = useQuery(
+ GET_MACHINES
+ )
+
+ const loading = messagesLoading && machinesLoading
+
+ const machineOptions =
+ machinesData &&
+ R.map(
+ it => ({ code: it.deviceId, display: it.name }),
+ R.path(['machines'])(machinesData)
+ )
+
+ const elements = [
+ {
+ header: 'Message name',
+ width: 400,
+ size: 'sm',
+ textAlign: 'left',
+ view: it => it.event
+ },
+ {
+ header: 'Edit',
+ width: 120,
+ size: 'sm',
+ textAlign: 'center',
+ view: it => (
+