manifold/functions/src/emails.ts

225 lines
5.4 KiB
TypeScript
Raw Normal View History

import _ = require('lodash')
import { Answer } from '../../common/answer'
import { getProbability } from '../../common/calculate'
import { Comment } from '../../common/comment'
import { Contract } from '../../common/contract'
import { CREATOR_FEE } from '../../common/fees'
2022-02-08 11:26:33 +00:00
import { PrivateUser, User } from '../../common/user'
import { formatMoney, formatPercent } from '../../common/util/format'
2022-02-08 11:26:33 +00:00
import { sendTemplateEmail, sendTextEmail } from './send-email'
import { getPrivateUser, getUser, isProd } from './utils'
2022-01-10 22:07:44 +00:00
type market_resolved_template = {
userId: string
2022-01-10 22:07:44 +00:00
name: string
creatorName: string
question: string
outcome: string
payout: string
url: string
}
const toDisplayResolution = (
outcome: string,
prob: number,
resolutions?: { [outcome: string]: number }
) => {
if (outcome === 'MKT' && resolutions) return 'MULTI'
Free response (#47) * Answer datatype and MULTI outcome type for Contract * Create free answer contract * Automatically sort Tailwind classes with Prettier (#45) * Add Prettier Tailwind plugin * Autoformat Tailwind classes with Prettier * Allow for non-binary contracts in contract page and related components * logo with white inside, transparent bg * Create answer * Some UI for showing answers * Answer bet panel * Convert rest of calcuate file to generic multi contracts * Working betting with ante'd NONE answer * Numbered answers. Layout & calculation tweaks * Can bet. More layout tweaks! * Resolve answer UI * Resolve multi market * Resolved market UI * Fix feed and cards for multi contracts * Sell bets. Various fixes * Tweaks for trades page * Always dev mode * Create answer bet has isAnte: true * Fix card showing 0% for multi contracts * Fix grouped bets feed for multi outcomes * None option converted to none of the above label at bottom of list. Button to resolve none. * Tweaks to no answers yet, resolve button layout * Show ante bets on new answers in the feed * Update placeholder text for description * Consolidate firestore rules for subcollections * Remove Contract and Bet type params. Use string type for outcomes. * Increase char limit to 10k for answers. Preserve line breaks. * Don't show resolve options after answer chosen * Fix type error in script * Remove NONE resolution option * Change outcomeType to include 'MULTI' and 'FREE_RESPONSE' * Show bet probability change and payout when creating answer * User info change: also change answers * Append answers to contract field 'answers' * sort trades by resolved * Don't include trailing !:,.; in links * Stop flooring inputs into formatMoney * Revert "Stop flooring inputs into formatMoney" This reverts commit 2f7ab1842916bc903e60231cbf45b934db2f2658. * Consistently floor user.balance * Expand create panel on focus From Richard Hanania's feedback * welcome email: include link to manifold * Fix home page in dev on branches that are not free-response * Close emails (#50) * script init for stephen dev * market close emails * order of operations * template email * sendMarketCloseEmail: handle unsubscribe * remove debugging * marketCloseEmails: every hour * sendMarketCloseEmails: check undefined * marketCloseEmails: "every hour" => "every 1 hours" * Set up a read API using Vercel serverless functions (#49) * Set up read API using Vercel serverless functions Featuring: /api/v0/markets /api/v0/market/[contractId] /api/v0/slug/[contractSlug] * Include tags in API * Tweaks. Remove filter for only binary contract * Fix bet probability change for NO bets * Put back isProd calculation Co-authored-by: Austin Chen <akrolsmir@gmail.com> Co-authored-by: mantikoros <sgrugett@gmail.com> Co-authored-by: mantikoros <95266179+mantikoros@users.noreply.github.com>
2022-02-17 23:00:19 +00:00
const display = {
YES: 'YES',
NO: 'NO',
CANCEL: 'N/A',
MKT: formatPercent(prob),
}[outcome]
return display === undefined ? `#${outcome}` : display
}
export const sendMarketResolutionEmail = async (
userId: string,
payout: number,
creator: User,
contract: Contract,
resolution: string,
resolutionProbability?: number,
resolutions?: { [outcome: string]: number }
) => {
const privateUser = await getPrivateUser(userId)
if (
!privateUser ||
privateUser.unsubscribedFromResolutionEmails ||
!privateUser.email
)
return
const user = await getUser(userId)
if (!user) return
const prob = resolutionProbability ?? getProbability(contract.totalShares)
const outcome = toDisplayResolution(resolution, prob, resolutions)
2022-01-10 22:07:44 +00:00
const subject = `Resolved ${outcome}: ${contract.question}`
2022-01-10 22:07:44 +00:00
const templateData: market_resolved_template = {
userId: user.id,
2022-01-10 22:07:44 +00:00
name: user.name,
creatorName: creator.name,
question: contract.question,
outcome,
payout: `${Math.round(payout)}`,
url: `https://manifold.markets/${creator.username}/${contract.slug}`,
}
2022-01-10 22:07:44 +00:00
// Modify template here:
// https://app.mailgun.com/app/sending/domains/mg.manifold.markets/templates/edit/market-resolved/initial
// Mailgun username: james@mantic.markets
await sendTemplateEmail(
privateUser.email,
subject,
'market-resolved',
templateData
)
}
2022-02-08 11:26:33 +00:00
export const sendWelcomeEmail = async (
user: User,
privateUser: PrivateUser
) => {
const firstName = user.name.split(' ')[0]
await sendTextEmail(
privateUser.email || '',
'Welcome to Manifold Markets!',
`Hi ${firstName},
Thanks for joining us! We can't wait to see what markets you create.
2022-02-22 22:35:53 +00:00
2022-02-08 11:26:33 +00:00
Questions? Feedback? I'd love to hear from you - just reply to this email!
2022-02-22 22:35:53 +00:00
2022-02-08 11:26:33 +00:00
Or come chat with us on Discord: https://discord.gg/eHQBNBqXuh
Best,
Austin from Manifold
https://manifold.markets/`
2022-02-08 11:26:33 +00:00
)
}
export const sendMarketCloseEmail = async (
user: User,
privateUser: PrivateUser,
contract: Contract
) => {
if (
!privateUser ||
privateUser.unsubscribedFromResolutionEmails ||
!privateUser.email
)
return
const { username, name, id: userId } = user
const firstName = name.split(' ')[0]
const { question, pool: pools, slug } = contract
const pool = formatMoney(_.sum(_.values(pools)))
const url = `https://manifold.markets/${username}/${slug}`
await sendTemplateEmail(
privateUser.email,
'Your market has closed',
'market-close',
{
name: firstName,
question,
pool,
url,
userId,
creatorFee: (CREATOR_FEE * 100).toString(),
}
)
}
export const sendNewCommentEmail = async (
userId: string,
commentCreator: User,
comment: Comment,
contract: Contract
) => {
const privateUser = await getPrivateUser(userId)
if (
!privateUser ||
!privateUser.email ||
privateUser.unsubscribedFromCommentEmails
)
return
const { question, creatorUsername, slug } = contract
const marketUrl = `https://manifold.markets/${creatorUsername}/${slug}`
const unsubscribeUrl = `https://us-central1-${
isProd ? 'mantic-markets' : 'dev-mantic-markets'
}.cloudfunctions.net/unsubscribe?id=${userId}&type=market-comment`
const { name: commentorName, avatarUrl: commentorAvatarUrl } = commentCreator
const { text } = comment
const subject = `Comment on ${question}`
const from = `${commentorName} <info@manifold.markets>`
await sendTemplateEmail(
privateUser.email,
subject,
'market-comment',
{
commentorName,
commentorAvatarUrl: commentorAvatarUrl ?? '',
comment: text,
marketUrl,
unsubscribeUrl,
},
{ from }
)
}
export const sendNewAnswerEmail = async (
answer: Answer,
contract: Contract
) => {
// Send to just the creator for now.
const { creatorId: userId } = contract
const privateUser = await getPrivateUser(userId)
if (
!privateUser ||
!privateUser.email ||
privateUser.unsubscribedFromAnswerEmails
)
return
const { question, creatorUsername, slug } = contract
const { name, avatarUrl, text } = answer
const marketUrl = `https://manifold.markets/${creatorUsername}/${slug}`
const unsubscribeUrl = `https://us-central1-${
isProd ? 'mantic-markets' : 'dev-mantic-markets'
}.cloudfunctions.net/unsubscribe?id=${userId}&type=market-answer`
const subject = `New answer on ${question}`
const from = `${name} <info@manifold.markets>`
await sendTemplateEmail(
privateUser.email,
subject,
'market-answer',
{
name,
avatarUrl: avatarUrl ?? '',
answer: text,
marketUrl,
unsubscribeUrl,
},
{ from }
)
}