configuration/findUsersWithSessionInMonths.sh
... ...
@@ -0,0 +1,27 @@
1
+#!/bin/bash
2
+# Usage: ${0} {MONGO-URI} {MONTHS}
3
+# Example: findUsersWithSessionInMonths.sh "mongodb://localhost/winddb?replicaSet=rs0" 12
4
+echo '
5
+var cutoff = new Date();
6
+cutoff.setMonth(cutoff.getMonth() - '${2}');
7
+
8
+var activeUsers = db.SESSIONS.distinct(
9
+ "SESSION_ATTRIBUTES.SESSION_ATTRIBUTE_VALUE.SESSION_PRINCIPAL_REALM_VALUE",
10
+ { SESSION_START_TIMESTAMP: { $gte: cutoff } }
11
+).flat();
12
+
13
+db.USERS.find(
14
+ {
15
+ EMAIL_VALIDATED: true,
16
+ EMAIL: { $exists: true, $ne: "" },
17
+ NAME: { $in: activeUsers },
18
+ $or: [
19
+ { DID_OPT_OUT_OF_FEATURE_AND_COMMUNITY_EMAILS: { $exists: false } },
20
+ { DID_OPT_OUT_OF_FEATURE_AND_COMMUNITY_EMAILS: false }
21
+ ]
22
+ },
23
+ { EMAIL: 1, FULLNAME: 1, NAME: 1, _id: 0 }
24
+).forEach(function(u) {
25
+ var name = (u.FULLNAME && u.FULLNAME.trim()) || u.NAME || "user";
26
+ print(u.EMAIL + "," + name);
27
+})' | mongosh "${1}"
configuration/sendEmails.md
... ...
@@ -0,0 +1,89 @@
1
+# sendEmails.sh — Personalized Email Sender
2
+
3
+Bash script for sending personalized HTML emails via SMTP (AWS WorkMail).
4
+
5
+## Prerequisites
6
+
7
+- `curl` with SMTP/TLS support
8
+- `base64` (coreutils)
9
+- SMTP credentials for the WorkMail account (`support@sapsailing.com`)
10
+
11
+## Files
12
+
13
+| File | Purpose |
14
+|---|---|
15
+| `sendEmails.sh` | The sending script |
16
+| `emailTemplate.html` | HTML email template with `{{NAME}}` placeholder |
17
+| `recipients.csv` | CSV file with recipient list |
18
+
19
+## CSV Format
20
+
21
+```csv
22
+email,name
23
+alice@example.com,Alice
24
+bob@example.com,Bob
25
+sailor42@example.com,sailor42
26
+```
27
+
28
+- **email** — recipient address
29
+- **name** — inserted into the salutation ("Dear {{NAME}},"). Use the user's first name if known, otherwise their username/nickname.
30
+- The header row is auto-detected and skipped if the first field starts with `email` (case-insensitive).
31
+
32
+## Usage
33
+
34
+```bash
35
+./sendEmails.sh [--dry-run] <recipients.csv> [template.html]
36
+```
37
+
38
+| Argument | Required | Default | Description |
39
+|---|---|---|---|
40
+| `--dry-run` | no | — | Preview personalized emails without sending |
41
+| `recipients.csv` | yes | — | Path to the CSV file |
42
+| `template.html` | no | `/tmp/emailTemplate.html` | Path to the HTML template |
43
+
44
+## Environment Variables
45
+
46
+| Variable | Required | Description |
47
+|---|---|---|
48
+| `SMTP_USER` | yes (unless `--dry-run`) | SMTP username for WorkMail |
49
+| `SMTP_PASS` | yes (unless `--dry-run`) | SMTP password for WorkMail |
50
+
51
+## Examples
52
+
53
+Preview all emails without sending:
54
+
55
+```bash
56
+./sendEmails.sh --dry-run recipients.csv
57
+```
58
+
59
+Send using a custom template:
60
+
61
+```bash
62
+export SMTP_USER="your-smtp-username"
63
+export SMTP_PASS="your-smtp-password"
64
+./sendEmails.sh recipients.csv myTemplate.html
65
+```
66
+
67
+## Configuration
68
+
69
+These values are set at the top of the script and can be adjusted:
70
+
71
+| Variable | Default | Description |
72
+|---|---|---|
73
+| `FROM_ADDR` | `SAP Sailing Analytics <support@sapsailing.com>` | Sender display name and address |
74
+| `SUBJECT` | `Your SAP Sailing Analytics Account — Open Source Transition Update` | Email subject line |
75
+| `SMTP_URL` | `smtps://smtp.mail.eu-west-1.awsapps.com:465` | WorkMail SMTP endpoint |
76
+
77
+## How It Works
78
+
79
+1. Reads the CSV line by line, skipping the header row.
80
+2. For each recipient, replaces `{{NAME}}` in the HTML template with the name from the CSV.
81
+3. Constructs a MIME message with UTF-8 base64-encoded subject and body.
82
+4. Sends via `curl` over SMTPS to the WorkMail endpoint.
83
+5. Waits 1 second between sends to respect SES rate limits.
84
+
85
+## Recommended Workflow
86
+
87
+1. **Preview** — Run with `--dry-run` and review the output.
88
+2. **Test** — Send to your own address first (`echo "email,name" > test.csv && echo "you@example.com,YourName" >> test.csv`).
89
+3. **Send** — Run against the full recipient list.
configuration/sendEmails.sh
... ...
@@ -0,0 +1,118 @@
1
+#!/usr/bin/env bash
2
+set -euo pipefail
3
+
4
+FROM_ADDR="SAP Sailing Analytics <support@sapsailing.com>"
5
+SUBJECT="SAP Sailing Analytics — Open Source Transition Update"
6
+SMTP_URL="smtps://smtp.mail.eu-west-1.awsapps.com:465"
7
+
8
+usage() {
9
+ cat <<'EOF'
10
+Usage: sendEmails.sh [--dry-run] <recipients.csv> [template.html]
11
+
12
+ recipients.csv CSV file with columns: email,name
13
+ template.html HTML template with {{NAME}} placeholder
14
+ (default: /tmp/emailTemplate.html)
15
+ --dry-run Print personalized emails to stdout without sending
16
+
17
+Environment variables:
18
+ SMTP_USER SMTP username (required unless --dry-run)
19
+ SMTP_PASS SMTP password (required unless --dry-run)
20
+
21
+CSV format example:
22
+ email,name
23
+ alice@example.com,Alice
24
+ bob@example.com,Bob
25
+EOF
26
+ exit 1
27
+}
28
+
29
+DRY_RUN=false
30
+if [[ "${1:-}" == "--dry-run" ]]; then
31
+ DRY_RUN=true
32
+ shift
33
+fi
34
+
35
+CSV="${1:-}"
36
+TEMPLATE="${2:-/tmp/emailTemplate.html}"
37
+
38
+[[ -z "$CSV" ]] && usage
39
+[[ ! -f "$CSV" ]] && { echo "Error: CSV file not found: $CSV"; exit 1; }
40
+[[ ! -f "$TEMPLATE" ]] && { echo "Error: Template file not found: $TEMPLATE"; exit 1; }
41
+
42
+if [[ "$DRY_RUN" == false ]]; then
43
+ [[ -z "${SMTP_USER:-}" ]] && { echo "Error: SMTP_USER not set"; exit 1; }
44
+ [[ -z "${SMTP_PASS:-}" ]] && { echo "Error: SMTP_PASS not set"; exit 1; }
45
+fi
46
+
47
+TEMPLATE_BODY=$(cat "$TEMPLATE")
48
+
49
+SENT=0
50
+FAILED=0
51
+SKIPPED_HEADER=false
52
+
53
+while IFS= read -r line || [[ -n "$line" ]]; do
54
+ # Skip empty lines
55
+ [[ -z "${line// }" ]] && continue
56
+
57
+ # Skip header row
58
+ if [[ "$SKIPPED_HEADER" == false ]]; then
59
+ SKIPPED_HEADER=true
60
+ if echo "$line" | grep -qi "^email"; then
61
+ continue
62
+ fi
63
+ fi
64
+
65
+ EMAIL=$(echo "$line" | cut -d',' -f1 | xargs)
66
+ NAME=$(echo "$line" | cut -d',' -f2- | xargs)
67
+
68
+ [[ -z "$EMAIL" ]] && continue
69
+
70
+ BODY="${TEMPLATE_BODY//\{\{NAME\}\}/$NAME}"
71
+
72
+ if [[ "$DRY_RUN" == true ]]; then
73
+ echo "========================================"
74
+ echo "To: $EMAIL"
75
+ echo "From: $FROM_ADDR"
76
+ echo "Subject: $SUBJECT"
77
+ echo "----------------------------------------"
78
+ echo "$BODY"
79
+ echo ""
80
+ SENT=$((SENT + 1))
81
+ continue
82
+ fi
83
+
84
+ MIME_MSG=$(cat <<MIME
85
+From: $FROM_ADDR
86
+To: $EMAIL
87
+Subject: =?UTF-8?B?$(echo -n "$SUBJECT" | base64 -w0)?=
88
+MIME-Version: 1.0
89
+Content-Type: text/html; charset=UTF-8
90
+Content-Transfer-Encoding: base64
91
+
92
+$(echo -n "$BODY" | base64 -w76)
93
+MIME
94
+ )
95
+
96
+ if echo "$MIME_MSG" | curl --silent --show-error \
97
+ --url "$SMTP_URL" \
98
+ --ssl-reqd \
99
+ --mail-from "support@sapsailing.com" \
100
+ --mail-rcpt "$EMAIL" \
101
+ --user "${SMTP_USER}:${SMTP_PASS}" \
102
+ --upload-file -; then
103
+ echo "Sent to $EMAIL ($NAME)"
104
+ SENT=$((SENT + 1))
105
+ else
106
+ echo "FAILED: $EMAIL ($NAME)" >&2
107
+ FAILED=$((FAILED + 1))
108
+ fi
109
+
110
+ sleep 1
111
+done < "$CSV"
112
+
113
+echo ""
114
+if [[ "$DRY_RUN" == true ]]; then
115
+ echo "Dry run complete. $SENT emails previewed."
116
+else
117
+ echo "Done. Sent: $SENT, Failed: $FAILED"
118
+fi
java/com.sap.sse.replication.interfaces/src/com/sap/sse/replication/FullyInitializedReplicableTracker.java
... ...
@@ -130,7 +130,8 @@ public class FullyInitializedReplicableTracker<R extends Replicable<?, ?>> exten
130 130
* {@link #waitForService(long)}). If no such service object can be found before timing out, {@code null}
131 131
* is returned. Once a service object has been retrieved and a non-{@code null} {@link #replicationServiceTracker}
132 132
* has been provided at construction time, the {@link ReplicationService} is obtained from that tracker by
133
- * waiting for it at least {@code timeoutInMillis} milliseconds and then
133
+ * waiting for it at least {@code timeoutInMillis} milliseconds and then is asked to wait for the replication
134
+ * to be fully initialized, so in particular having received and incorporated the initial load.
134 135
*
135 136
* @param timeoutInMillis
136 137
* 0 means indefinite wait time