Hello World, I'm on Vacation!¶
As part of this tutorial, we will build a vacation auto-responder.
Requirements¶
Before starting, make sure to register for Sobamail, download and install the client and login to your account using the native desktop client.
Though not strictly needed, you may want to be able to manipulate SQLite databases. So either:
- Download the official CLI tool from sqlite.org
- Use the CLI tool provided by your operating system,
- Use a GUI tool like DB Browser for SQLite.
Sobamail Applications¶
To reiterate from the concepts section, Sobamail applications, just like regular web applications, are split into two main components that work together:
- A Mutator module that handles all business logic and state changes,
- A user interface that handles all user interaction. It's a regular frontend application.
Apps need to be instantiated before they can be used. Once deployed, Sobamail applications are immutable, which would normally make app development quite impractical. That's why Sobamail platform has two deployment modes for applications: Developer mode and production mode.
Mutations generated by an app in developer mode are not replicated since it's impossible to guarantee convergence.
We will start by instantiating the Sobamail starter app in developer mode.
The Local App Store¶
Each Mailbox replica has its own local app store called Application Manager. It can be used to instantiate apps in developer mode. To do this:
- Launch Sobamail
- In the folders widget, expand the
Apps folder. This folder contains the list of instantiated mailbox-wide Applications.
- Find and expand the Application Manager folder. You should see a single entry with a garbled text label. That's the application instance id of your Application Manager app of your mailbox. Click it to launch the Application Manager GUI.
- Click the "Instantiate In Dev Mode" button.
- Enter your desired app id and app name. Do note that, once published, it's not possible to change the app id of a Sobamail application.
- Click "Submit" to instantiate your app.
Note
You are supposed to use only subdomains of domain names you control as your application id. Sobamail reserves the right to remove your application from the Sobamail Root App Store without any notice and without providing any reason whatsoever.
By definition, sobamail users are given control of the <user>.user.app.<domain.name>
domain with all of its subdomains. So if your Sobamail username is alice@example.com
, you can use eg. responder.alice.user.app.example.com
as application id.
Preliminaries¶
Now you should see an your new app instance in the apps folder, under your given app name. Click its instance id to launch the initial html file.
Follow on-screen instructions to locate the develroot.
Note
Now is a good time to initialize a git repository!
Let's have a detailed look at the starter mutator before delving into implementation details:
import "soba://computer/R1"; // (1)
import { // (2)
DeleteRow,
} from "https://sobamail.com/schema/base/v1?sha224=LbrFSXuQxm2gn4-FaglNXRQbcv7kAz9Zew-p1A";
export default class Mutator { // (3)
static id = "responder.alice.app.user.example.com"; // (4)
static name = "Vacation Responder"; // (5)
static version = "1.0.0.0"; // (6)
static objects = new Map([ // (7)
[ DeleteRow.KEY, false ],
]);
constructor() { // (8)
// TODO: Create the database schema
// TODO: Perform any sanity checks
}
process(message, metadata) { // (9)
// TODO: Implement the app logic
}
}
-
Sobamail runtime is versioned to ensure convergeance on all replicas. First thing to do is to choose a computer release to run your application. There is currently only one computer to choose: Replicated-1. It can be imported using
"soba://computer/R1"
as import string. -
For the same reason, all modules use hash values in import statements to guarantee that the same code is run in all replicas from the root module down to the last leaf in the import tree. See Imports for more information.
You can find the latest hash values in the sobamail/base repository:
Some examples:
-
All root modules must contain a default exported class.
-
The default class must have a static
id
variable of typeString
that must match the value in theinstances
table. -
The default class must have a static
name
variable of typeString
that must match the value in theinstances
table. -
The default class must have a static
version
variable of typeString
that must match the value in theinstances
table. -
The default class must have a static
objects
variable of typeMap<String, Boolean>
that should match the values in theassoc
table. Since an app that doesn't react to any object at all would not be useful, the Map object can not be empty.-
The map key is a string of format
"{Namespace}Name"
. See Events for details. -
The map value is a boolean whose value must be
false
.
-
-
In Sobamail, events get processed in two stages: First, The Mutator class is instantiated by the JS runtime. This runs the constructor where the user defines the app schema and performs various sanity checks. The constructor must throw an
Error
if the application environment can not be validated. -
Now that the runtime has a fresh Mutator instance at hand, it invokes the
process
method with two arguments:message
: The message object that triggered the eventmetadata
: Contains metadata about the processing environment and the provenance of the given message.
If the
process()
method doesn't throw, the event is considered to be processed sucessfully.The return value of the
process()
method is discarded.
Implementation¶
A vacation auto-responder is a simple application that will automatically respond to emails with a vacation message. It will have to know about all incoming emails. So we must add the Message
object to its list of objects of interest.
Let's start by importing the Message class:
import {
DeleteRow,
+ Message,
} from "https://sobamail.com/schema/base/v1?sha224=LbrFSXuQxm2gn4-FaglNXRQbcv7kAz9Zew-p1A";
... and add it to the object keys of interest:
export default class Mutator {
static id = "responder.test.user.app.example.com";
static name = "Vacation Auto-Responder";
static version = "1.0.0.0";
static objects = new Map([
[ DeleteRow.KEY, false ],
+ [ Message.KEY, false ],
]);
Now, let's fill in the process
method so that it replies to all incoming messages:
// ...
process(message, metadata) {
// If you prefer
soba.log.info("Hello World!");
// FYI: Show incoming data
soba.log.info(`metadata: ${JSON.stringify(metadata, null, 2)}`);
soba.log.info(` message: ${JSON.stringify(message, null, 2)}`);
let reply = new Message();
reply.from = [ {address : soba.app.account()} ];
reply.to = [ {name : message.from[0][0], address : message.from[0][1]} ];
reply.subject = "Hello World! I'm on Vacation!";
reply.bodyText = "I am on vacation and will not be able to respond to" +
" your message in a timely manner." +
"\nPlease contact me some other time.";
reply.bodyHtml = "<p>" +
"I am on vacation and will not be able to respond to" +
" your message in a timely manner.<br>" +
"\nPlease contact me some other time." +
"</p>";
soba.log.info(
`Sent vacation response to '${reply.to[0].name} <${reply.to[0].address}>'`);
soba.mail.send(reply);
}
// ...
There it is! One last thing we need to do is to refresh the objects of interest for our application. You need to do this every time you change the objects map in developer mode. On production deployments, this is done automatically by the Application Manager during the app instantiation process.
To do this, go to the Application Manager again, click "Instances", find your instance id, (it should be easy as it should have a developer mode mark: ) select it and click "Reload Objects" from the top.
There it is! Now test it by sending an email from another account.